From ac6999924f7586d2b0f3337bdc1dfe9c414a2914 Mon Sep 17 00:00:00 2001 From: "emdee@spm.plastiras.org" Date: Sat, 10 Feb 2024 23:52:50 +0000 Subject: [PATCH] sound fixes --- requirements.txt | 3 +- toxygen/__main__.py | 2 +- toxygen/app.py | 24 +- toxygen/av/calls.py | 83 ++- toxygen/av/calls_manager.py | 7 +- toxygen/bootstrap/bootstrap.py | 6 +- toxygen/contacts/basecontact.py | 2 +- toxygen/contacts/contact.py | 2 +- toxygen/contacts/contact_menu.py | 2 +- toxygen/contacts/contacts_manager.py | 2 +- toxygen/contacts/group_chat.py | 2 +- toxygen/contacts/group_factory.py | 2 +- toxygen/contacts/profile.py | 4 +- toxygen/file_transfers/file_transfers.py | 4 +- toxygen/groups/groups_service.py | 6 +- toxygen/groups/peers_list.py | 2 +- toxygen/messenger/messenger.py | 4 +- toxygen/middleware/callbacks.py | 20 +- toxygen/middleware/threads.py | 6 +- toxygen/middleware/tox_factory.py | 18 +- toxygen/notifications/sound.py | 4 +- toxygen/tests/test_gdb.py | 4 +- toxygen/tests/tests_socks.py | 14 +- .../third_party/qweechat/preferences.py.bak | 57 ++ toxygen/third_party/qweechat/qweechat.py.bak | 569 ++++++++++++++++++ toxygen/ui/av_widgets.py | 11 +- toxygen/ui/contact_items.py | 2 +- toxygen/ui/group_peers_list.py | 2 +- toxygen/ui/groups_widgets.py | 2 +- toxygen/ui/menu.py | 8 +- toxygen/ui/messages_widgets.py | 2 +- toxygen/ui/peer_screen.py | 2 +- 32 files changed, 765 insertions(+), 113 deletions(-) create mode 100644 toxygen/third_party/qweechat/preferences.py.bak create mode 100644 toxygen/third_party/qweechat/qweechat.py.bak diff --git a/requirements.txt b/requirements.txt index 35df599..f2b4629 100644 --- a/requirements.txt +++ b/requirements.txt @@ -8,10 +8,11 @@ QtPy >= 2.4.1 PyAudio >= 0.2.13 numpy >= 1.26.1 opencv_python >= 4.8.0 -pydenticon >= 0.3.1 pillow >= 10.2.0 gevent >= 23.9.1 +pydenticon >= 0.3.1 greenlet >= 2.0.2 +sounddevice >= 0.3.15 # this is optional coloredlogs >= 15.0.1 # this is optional diff --git a/toxygen/__main__.py b/toxygen/__main__.py index 0b7a6b4..18f7821 100644 --- a/toxygen/__main__.py +++ b/toxygen/__main__.py @@ -14,7 +14,7 @@ faulthandler.enable() import warnings warnings.filterwarnings('ignore') -import tox_wrapper.tests.support_testing as ts +import toxygen_wrapper.tests.support_testing as ts try: from trepan.interfaces import server as Mserver from trepan.api import debug diff --git a/toxygen/app.py b/toxygen/app.py index 05e6df4..fc58e21 100644 --- a/toxygen/app.py +++ b/toxygen/app.py @@ -38,7 +38,7 @@ from middleware import threads import middleware.callbacks as callbacks import updater.updater as updater from middleware.tox_factory import tox_factory -import tox_wrapper.toxencryptsave as tox_encrypt_save +import toxygen_wrapper.toxencryptsave as tox_encrypt_save import user_data.toxes from user_data import settings from user_data.settings import get_user_config_path, merge_args_into_settings @@ -76,7 +76,7 @@ from ui.widgets_factory import WidgetsFactory from user_data.backup_service import BackupService import styles.style # TODO: dynamic loading -import tox_wrapper.tests.support_testing as ts +import toxygen_wrapper.tests.support_testing as ts global LOG import logging @@ -114,7 +114,7 @@ def setup_logging(oArgs) -> None: LOG.setLevel(oArgs.loglevel) LOG.trace = lambda l: LOG.log(0, repr(l)) - LOG.info(f"Setting loglevel to {oArgs.loglevel!s}") + LOG.info(f"Setting loglevel to {oArgs.loglevel}") if oArgs.loglevel < 20: # opencv debug @@ -164,7 +164,7 @@ class App: setup_logging(oArgs) # sys.stderr.write( 'Command line args: ' +repr(oArgs) +'\n') LOG.info("Command line: " +' '.join(sys.argv[1:])) - LOG.debug(f'oArgs = {oArgs!r}') + LOG.debug(f'oArgs = {oArgs}') LOG.info("Starting toxygen version " +version) self._version = version @@ -238,8 +238,8 @@ class App: if self._uri is not None: self._ms.add_contact(self._uri) except Exception as e: - LOG.error(f"Error loading profile: {e!s}") - sys.stderr.write(' iMain(): ' +f"Error loading profile: {e!s}" \ + LOG.error(f"Error loading profile: {e}") + sys.stderr.write(' iMain(): ' +f"Error loading profile: {e}" \ +'\n' + traceback.format_exc()+'\n') util_ui.message_box(str(e), util_ui.tr('Error loading profile')) @@ -350,7 +350,7 @@ class App: self._app.setStyleSheet(style) def _load_app_styles(self) -> None: - LOG.debug(f"_load_app_styles {list(settings.built_in_themes().keys())!r}") + LOG.debug(f"_load_app_styles {list(settings.built_in_themes().keys())}") # application color scheme if self._settings['theme'] in ['', 'default']: return for theme in settings.built_in_themes().keys(): @@ -478,7 +478,7 @@ class App: # Threads def _start_threads(self, initial_start=True) -> None: - LOG.debug(f"_start_threads before: {threading.enumerate()!r}") + LOG.debug(f"_start_threads before: {threading.enumerate()}") # init thread self._init = threads.InitThread(self._tox, self._plugin_loader, @@ -487,7 +487,7 @@ class App: initial_start) self._init.start() def te(): return [t.name for t in threading.enumerate()] - LOG.debug(f"_start_threads init: {te()!r}") + LOG.debug(f"_start_threads init: {te()}") # starting threads for tox iterate and toxav iterate self._main_loop = threads.ToxIterateThread(self._tox, app=self) @@ -498,7 +498,7 @@ class App: if initial_start: threads.start_file_transfer_thread() - LOG.debug(f"_start_threads after: {[t.name for t in threading.enumerate()]!r}") + LOG.debug(f"_start_threads after: {[t.name for t in threading.enumerate()]}") def _stop_threads(self, is_app_closing=True) -> None: LOG.debug("_stop_threads") @@ -917,7 +917,7 @@ class App: if status > 0: LOG.info(f"Connected # {i}" +' : ' +repr(status)) break - LOG.trace(f"Connected status #{i}: {status!r}") + LOG.trace(f"Connected status #{i}: {status}") self.loop(2) def _test_env(self) -> None: @@ -995,7 +995,7 @@ class App: self._ms.log_console() def _test_main(self) -> None: - from toxygen_tox_wrapper.tox_wrapper.tests.tests_wrapper import main as tests_main + from toxygen_toxygen_wrapper.toxygen_wrapper.tests.tests_wrapper import main as tests_main LOG.debug("_test_main") if not self._tox: return title = 'Extended Test Suite' diff --git a/toxygen/av/calls.py b/toxygen/av/calls.py index f60ac8a..168f837 100644 --- a/toxygen/av/calls.py +++ b/toxygen/av/calls.py @@ -3,9 +3,9 @@ import time import threading import itertools -from tox_wrapper.toxav_enums import * -from tox_wrapper.tests import support_testing as ts -from tox_wrapper.tests.support_testing import LOG_ERROR, LOG_WARN, LOG_INFO, LOG_DEBUG, LOG_TRACE +from toxygen_wrapper.toxav_enums import * +from toxygen_wrapper.tests import support_testing as ts +from toxygen_wrapper.tests.support_testing import LOG_ERROR, LOG_WARN, LOG_INFO, LOG_DEBUG, LOG_TRACE with ts.ignoreStderr(): import pyaudio @@ -14,7 +14,7 @@ from av.call import Call import common.tox_save from utils import ui as util_ui -import tox_wrapper.tests.support_testing as ts +import toxygen_wrapper.tests.support_testing as ts from middleware.threads import invoke_in_main_thread from middleware.threads import BaseThread @@ -38,10 +38,10 @@ class AV(common.tox_save.ToxAvSave): s = settings if 'video' not in s: LOG.warn("AV.__init__ 'video' not in s" ) - LOG.debug(f"AV.__init__ {s!r}" ) + LOG.debug(f"AV.__init__ {s}" ) elif 'device' not in s['video']: LOG.warn("AV.__init__ 'device' not in s.video" ) - LOG.debug(f"AV.__init__ {s['video']!r}" ) + LOG.debug(f"AV.__init__ {s['video']}" ) self._calls = {} # dict: key - friend number, value - Call instance @@ -71,12 +71,15 @@ class AV(common.tox_save.ToxAvSave): # was iOutput = self._settings._args.audio['output'] iInput = self._settings['audio']['input'] self.lPaSampleratesI = ts.lSdSamplerates(iInput) + if not self.lPaSampleratesI: LOG.warn(f"empty self.lPaSampleratesI iInput={iInput}") iOutput = self._settings['audio']['output'] self.lPaSampleratesO = ts.lSdSamplerates(iOutput) + if not self.lPaSampleratesO: LOG.warn(f"empty self.lPaSampleratesO iOutput={iOutput}") global oPYA oPYA = self._audio = pyaudio.PyAudio() def stop(self): + LOG_DEBUG(f"AV.CA stop {self._video_thread}") self._running = False self.stop_audio_thread() self.stop_video_thread() @@ -126,8 +129,7 @@ class AV(common.tox_save.ToxAvSave): self._audio_krate_tox_audio if audio_enabled else 0, self._audio_krate_tox_video if video_enabled else 0) except Exception as e: - LOG.debug(f"AV accept_call error from {friend_number} {self._running}" + - f"{e}") + LOG.debug(f"AV accept_call error from {friend_number} {self._running} {e}") raise if audio_enabled: # may raise @@ -194,26 +196,34 @@ class AV(common.tox_save.ToxAvSave): LOG_WARN(f"start_audio_thread device={iInput}") return LOG_DEBUG(f"start_audio_thread device={iInput}") - lPaSamplerates = ts.lSdSamplerates(iInput) + lPaSamplerates = ts.lSdSamplerates(iInput) if not(len(lPaSamplerates)): - e = f"No supported sample rates for device: audio[input]={iInput!r}" + e = f"No sample rates for device: audio[input]={iInput}" LOG_ERROR(f"start_audio_thread {e}") - #?? dunno - cancel call? - return - if not self._audio_rate_pa in lPaSamplerates: - LOG_WARN(f"{self._audio_rate_pa} not in {lPaSamplerates!r}") - if False: - self._audio_rate_pa = oPYA.get_device_info_by_index(iInput)['defaultSampleRate'] - else: - LOG_WARN(f"Setting audio_rate to: {lPaSamplerates[0]}") - self._audio_rate_pa = lPaSamplerates[0] - + #?? dunno - cancel call? - no let the user do it + # return + # just guessing here in case that's a false negative + lPaSamplerates = [round(oPYA.get_device_info_by_index(iInput)['defaultSampleRate'])] + if lPaSamplerates and self._audio_rate_pa in lPaSamplerates: + pass + elif lPaSamplerates: + LOG_WARN(f"{self._audio_rate_pa} not in {lPaSamplerates}") + self._audio_rate_pa = lPaSamplerates[0] + LOG_WARN(f"Setting audio_rate to: {lPaSamplerates[0]}") + elif 'defaultSampleRate' in oPYA.get_device_info_by_index(iInput): + self._audio_rate_pa = oPYA.get_device_info_by_index(iInput)['defaultSampleRate'] + else: + LOG_WARN(f"{self._audio_rate_pa} not in {lPaSamplerates}") + # a float is in here - must it be int? + if type(self._audio_rate_pa) == float: + self._audio_rate_pa = round(self._audio_rate_pa) try: LOG_DEBUG( f"start_audio_thread framerate: {self._audio_rate_pa}" \ +f" device: {iInput}" - +f" supported: {lPaSamplerates!r}") + +f" supported: {lPaSamplerates}") if self._audio_rate_pa not in lPaSamplerates: LOG_WARN(f"PAudio sampling rate was {self._audio_rate_pa} changed to {lPaSamplerates[0]}") + LOG_DEBUG(f"lPaSamplerates={lPaSamplerates}") self._audio_rate_pa = lPaSamplerates[0] if bSTREAM_CALLBACK: @@ -256,9 +266,10 @@ class AV(common.tox_save.ToxAvSave): # raise RuntimeError(e) return else: - LOG_DEBUG(f"start_audio_thread {self._audio_stream!r}") + LOG_DEBUG(f"start_audio_thread {self._audio_stream}") def stop_audio_thread(self): + LOG_DEBUG(f"stop_audio_thread {self._audio_stream}") if self._audio_thread is None: return @@ -279,11 +290,11 @@ class AV(common.tox_save.ToxAvSave): s = self._settings if 'video' not in s: LOG.warn("AV.__init__ 'video' not in s" ) - LOG.debug(f"start_video_thread {s!r}" ) + LOG.debug(f"start_video_thread {s}" ) raise RuntimeError("start_video_thread not 'video' in s)" ) elif 'device' not in s['video']: LOG.error("start_video_thread not 'device' in s['video']" ) - LOG.debug(f"start_video_thread {s['video']!r}" ) + LOG.debug(f"start_video_thread {s['video']}" ) raise RuntimeError("start_video_thread not 'device' ins s['video']" ) self._video_width = s['video']['width'] self._video_height = s['video']['height'] @@ -321,6 +332,7 @@ class AV(common.tox_save.ToxAvSave): self._video_thread.start() def stop_video_thread(self) -> None: + LOG_DEBUG(f"stop_video_thread {self._video_thread}") if self._video_thread is None: return @@ -331,7 +343,6 @@ class AV(common.tox_save.ToxAvSave): try: if not self._video_thread.is_alive(): break except: - # AttributeError: 'NoneType' object has no attribute 'join' break i = i + 1 else: @@ -349,12 +360,20 @@ class AV(common.tox_save.ToxAvSave): if self._out_stream is None: # was iOutput = self._settings._args.audio['output'] iOutput = self._settings['audio']['output'] - if not rate in self.lPaSampleratesO: - LOG.warn(f"{rate} not in {self.lPaSampleratesO!r}") - if False: - rate = oPYA.get_device_info_by_index(iOutput)['defaultSampleRate'] + if self.lPaSampleratesO and rate in self.lPaSampleratesO: + pass + elif self.lPaSampleratesO: + LOG.warn(f"{rate} not in {self.lPaSampleratesO}") LOG.warn(f"Setting audio_rate to: {self.lPaSampleratesO[0]}") rate = self.lPaSampleratesO[0] + elif 'defaultSampleRate' in oPYA.get_device_info_by_index(iOutput): + rate = round(oPYA.get_device_info_by_index(iOutput)['defaultSampleRate']) + LOG.warn(f"Setting rate to {rate} empty self.lPaSampleratesO") + else: + LOG.warn(f"Using rate {rate} empty self.lPaSampleratesO") + if type(rate) == float: + rate = round(rate) + try: with ts.ignoreStderr(): self._out_stream = oPYA.open(format=pyaudio.paInt16, @@ -372,7 +391,7 @@ class AV(common.tox_save.ToxAvSave): return iOutput = self._settings['audio']['output'] - LOG.debug(f"audio_chunk output_device_index={iOutput} rate={rate} channels={channels_count}") +#trace LOG.debug(f"audio_chunk output_device_index={iOutput} rate={rate} channels={channels_count}") self._out_stream.write(samples) # AV sending @@ -389,7 +408,6 @@ class AV(common.tox_save.ToxAvSave): for friend_num in self._calls: if self._calls[friend_num].out_audio: try: - # app.av.calls ERROR Error send_audio: 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 # app.av.calls ERROR Error send_audio audio_send_frame: This client is currently not in a call with the friend. self._toxav.audio_send_frame(friend_num, pcm, @@ -402,7 +420,8 @@ class AV(common.tox_save.ToxAvSave): # invoke_in_main_thread(util_ui.message_box, # str(e), # util_ui.tr("Error send_audio audio_send_frame")) - pass + # raise #? stop ? endcall? + self.stop_audio_thread() def send_audio(self) -> None: """ diff --git a/toxygen/av/calls_manager.py b/toxygen/av/calls_manager.py index b919c90..d7bcab6 100644 --- a/toxygen/av/calls_manager.py +++ b/toxygen/av/calls_manager.py @@ -2,6 +2,7 @@ import sys import threading +import traceback import av.calls from messenger.messages import * @@ -88,7 +89,9 @@ class CallsManager: try: self._callav.call_accept_call(friend_number, audio, video) except Exception as e: + # LOG.error(f"accept_call _call.accept_call ERROR for {friend_number} {e}") + LOG.debug(traceback.print_exc()) self._main_screen.call_finished() if hasattr(self._main_screen, '_settings') and \ 'audio' in self._main_screen._settings and \ @@ -100,11 +103,11 @@ class CallsManager: elif hasattr(self._main_screen, '_settings') and \ hasattr(self._main_screen._settings, 'audio') and \ 'input' not in self._main_screen._settings['audio']: - LOG.warn(f"'audio' not in {self._main_screen._settings!r}") + LOG.warn(f"'audio' not in {self._main_screen._settings}") elif hasattr(self._main_screen, '_settings') and \ hasattr(self._main_screen._settings, 'audio') and \ 'input' not in self._main_screen._settings['audio']: - LOG.warn(f"'audio' not in {self._main_screen._settings!r}") + LOG.warn(f"'audio' not in {self._main_screen._settings}") else: LOG.warn(f"_settings not in self._main_screen") util_ui.message_box(str(e), diff --git a/toxygen/bootstrap/bootstrap.py b/toxygen/bootstrap/bootstrap.py index 0e1a666..506b248 100644 --- a/toxygen/bootstrap/bootstrap.py +++ b/toxygen/bootstrap/bootstrap.py @@ -11,9 +11,9 @@ except ImportError: certifi = None from user_data.settings import get_user_config_path -from tox_wrapper.tests.support_testing import _get_nodes_path -from tox_wrapper.tests.support_http import download_url -import tox_wrapper.tests.support_testing as ts +from toxygen_wrapper.tests.support_testing import _get_nodes_path +from toxygen_wrapper.tests.support_http import download_url +import toxygen_wrapper.tests.support_testing as ts global LOG import logging diff --git a/toxygen/contacts/basecontact.py b/toxygen/contacts/basecontact.py index ab9d3d7..b4b33f1 100644 --- a/toxygen/contacts/basecontact.py +++ b/toxygen/contacts/basecontact.py @@ -1,7 +1,7 @@ # -*- mode: python; indent-tabs-mode: nil; py-indent-offset: 4; coding: utf-8 -*- from user_data.settings import * from qtpy import QtCore, QtGui -from tox_wrapper.toxcore_enums_and_consts import TOX_PUBLIC_KEY_SIZE +from toxygen_wrapper.toxcore_enums_and_consts import TOX_PUBLIC_KEY_SIZE import utils.util as util import common.event as event import contacts.common as common diff --git a/toxygen/contacts/contact.py b/toxygen/contacts/contact.py index d034702..70b9318 100644 --- a/toxygen/contacts/contact.py +++ b/toxygen/contacts/contact.py @@ -146,7 +146,7 @@ class Contact(basecontact.BaseContact): message.mark_as_sent() except Exception as ex: # wrapped C/C++ object of type QLabel has been deleted - LOG.error(f"Mark as sent: {ex!s}") + LOG.error(f"Mark as sent: {ex}") # Message deletion diff --git a/toxygen/contacts/contact_menu.py b/toxygen/contacts/contact_menu.py index 0149940..6f45ca6 100644 --- a/toxygen/contacts/contact_menu.py +++ b/toxygen/contacts/contact_menu.py @@ -2,7 +2,7 @@ from qtpy import QtWidgets import utils.ui as util_ui -from tox_wrapper.toxcore_enums_and_consts import * +from toxygen_wrapper.toxcore_enums_and_consts import * global LOG import logging diff --git a/toxygen/contacts/contacts_manager.py b/toxygen/contacts/contacts_manager.py index f830930..8a429e0 100644 --- a/toxygen/contacts/contacts_manager.py +++ b/toxygen/contacts/contacts_manager.py @@ -9,7 +9,7 @@ from common.tox_save import ToxSave from contacts.group_peer_contact import GroupPeerContact from groups.group_peer import GroupChatPeer from middleware.callbacks import LOG_ERROR, LOG_WARN, LOG_INFO, LOG_DEBUG, LOG_TRACE -import tox_wrapper.toxcore_enums_and_consts as enums +import toxygen_wrapper.toxcore_enums_and_consts as enums # LOG=util.log global LOG diff --git a/toxygen/contacts/group_chat.py b/toxygen/contacts/group_chat.py index c6f11e0..c060e65 100644 --- a/toxygen/contacts/group_chat.py +++ b/toxygen/contacts/group_chat.py @@ -4,7 +4,7 @@ from contacts import contact from contacts.contact_menu import GroupMenuGenerator import utils.util as util from groups.group_peer import GroupChatPeer -from tox_wrapper import toxcore_enums_and_consts as constants +from toxygen_wrapper import toxcore_enums_and_consts as constants from common.tox_save import ToxSave from groups.group_ban import GroupBan diff --git a/toxygen/contacts/group_factory.py b/toxygen/contacts/group_factory.py index d7b26b7..4345c4b 100644 --- a/toxygen/contacts/group_factory.py +++ b/toxygen/contacts/group_factory.py @@ -2,7 +2,7 @@ from contacts.group_chat import GroupChat from common.tox_save import ToxSave -import tox_wrapper.toxcore_enums_and_consts as constants +import toxygen_wrapper.toxcore_enums_and_consts as constants global LOG import logging diff --git a/toxygen/contacts/profile.py b/toxygen/contacts/profile.py index e7d3c6f..3afcf2b 100644 --- a/toxygen/contacts/profile.py +++ b/toxygen/contacts/profile.py @@ -96,8 +96,8 @@ class Profile(basecontact.BaseContact, tox_save.ToxSave): self._timer.start() # Current thread 0x00007901a13ccb80 (most recent call first): -# File "/usr/local/lib/python3.11/site-packages/tox_wrapper/tox.py", line 826 in self_get_friend_list_size -# File "/usr/local/lib/python3.11/site-packages/tox_wrapper/tox.py", line 838 in self_get_friend_list +# File "/usr/local/lib/python3.11/site-packages/toxygen_wrapper/tox.py", line 826 in self_get_friend_list_size +# File "/usr/local/lib/python3.11/site-packages/toxygen_wrapper/tox.py", line 838 in self_get_friend_list # File "/mnt/o/var/local/src/toxygen/toxygen/contacts/contact_provider.py", line 45 in get_all_friends # File "/mnt/o/var/local/src/toxygen/toxygen/contacts/profile.py", line 90 in _reconnect # File "/usr/lib/python3.11/threading.py", line 1401 in run diff --git a/toxygen/file_transfers/file_transfers.py b/toxygen/file_transfers/file_transfers.py index a0a28df..9b31c5c 100644 --- a/toxygen/file_transfers/file_transfers.py +++ b/toxygen/file_transfers/file_transfers.py @@ -5,8 +5,8 @@ from time import time from common.event import Event from middleware.threads import invoke_in_main_thread -from tox_wrapper.tox import Tox -from tox_wrapper.toxcore_enums_and_consts import TOX_FILE_KIND, TOX_FILE_CONTROL +from toxygen_wrapper.tox import Tox +from toxygen_wrapper.toxcore_enums_and_consts import TOX_FILE_KIND, TOX_FILE_CONTROL from middleware.callbacks import LOG_ERROR, LOG_WARN, LOG_INFO, LOG_DEBUG, LOG_TRACE FILE_TRANSFER_STATE = { diff --git a/toxygen/groups/groups_service.py b/toxygen/groups/groups_service.py index e45a1e4..ec40104 100644 --- a/toxygen/groups/groups_service.py +++ b/toxygen/groups/groups_service.py @@ -4,9 +4,9 @@ import common.tox_save as tox_save import utils.ui as util_ui from groups.peers_list import PeersListGenerator from groups.group_invite import GroupInvite -import tox_wrapper.toxcore_enums_and_consts as constants -from tox_wrapper.toxcore_enums_and_consts import * -from tox_wrapper.tox import UINT32_MAX +import toxygen_wrapper.toxcore_enums_and_consts as constants +from toxygen_wrapper.toxcore_enums_and_consts import * +from toxygen_wrapper.tox import UINT32_MAX global LOG import logging diff --git a/toxygen/groups/peers_list.py b/toxygen/groups/peers_list.py index f5db042..c5aac34 100644 --- a/toxygen/groups/peers_list.py +++ b/toxygen/groups/peers_list.py @@ -1,5 +1,5 @@ from ui.group_peers_list import PeerItem, PeerTypeItem -from tox_wrapper.toxcore_enums_and_consts import * +from toxygen_wrapper.toxcore_enums_and_consts import * from ui.widgets import * diff --git a/toxygen/messenger/messenger.py b/toxygen/messenger/messenger.py index 50795bf..099ef35 100644 --- a/toxygen/messenger/messenger.py +++ b/toxygen/messenger/messenger.py @@ -3,8 +3,8 @@ import common.tox_save as tox_save import utils.ui as util_ui from messenger.messages import * -from tox_wrapper.tests.support_testing import assert_main_thread -from tox_wrapper.toxcore_enums_and_consts import TOX_MAX_MESSAGE_LENGTH +from toxygen_wrapper.tests.support_testing import assert_main_thread +from toxygen_wrapper.toxcore_enums_and_consts import TOX_MAX_MESSAGE_LENGTH global LOG import logging diff --git a/toxygen/middleware/callbacks.py b/toxygen/middleware/callbacks.py index 33e53cb..46080d8 100644 --- a/toxygen/middleware/callbacks.py +++ b/toxygen/middleware/callbacks.py @@ -3,9 +3,9 @@ import sys import os import threading from qtpy import QtGui -from tox_wrapper.toxcore_enums_and_consts import * -from tox_wrapper.toxav_enums import * -from tox_wrapper.tox import bin_to_string +from toxygen_wrapper.toxcore_enums_and_consts import * +from toxygen_wrapper.toxav_enums import * +from toxygen_wrapper.tox import bin_to_string import utils.ui as util_ui import utils.util as util from middleware.threads import invoke_in_main_thread, execute @@ -582,7 +582,7 @@ def group_peer_name(contacts_provider, groups_service): else: # FixMe: known signal to revalidate roles... #_peers = [(p._name, p._peer_id) for p in group.get_peers()] - LOG_TRACE(f"remove_peer group {group!r} has no peer_id={peer_id} in _peers!r") + LOG_TRACE(f"remove_peer group {group} has no peer_id={peer_id} in _peers!r") return return wrapped @@ -597,7 +597,7 @@ def group_peer_status(contacts_provider, groups_service): peer.status = peer_status else: # _peers = [(p._name, p._peer_id) for p in group.get_peers()] - LOG_TRACE(f"remove_peer group {group!r} has no peer_id={peer_id} in _peers!r") + LOG_TRACE(f"remove_peer group {group} has no peer_id={peer_id} in _peers!r") # TODO: add info message invoke_in_main_thread(groups_service.generate_peers_list) @@ -613,7 +613,7 @@ def group_topic(contacts_provider): invoke_in_main_thread(group.set_status_message, topic) else: _peers = [(p._name, p._peer_id) for p in group.get_peers()] - LOG_WARN(f"group_topic {group!r} has no peer_id={peer_id} in {_peers!r}") + LOG_WARN(f"group_topic {group} has no peer_id={peer_id} in {_peers}") # TODO: add info message return wrapped @@ -627,7 +627,7 @@ def group_moderation(groups_service, contacts_provider, contacts_manager, messen else: # FixMe: known signal to revalidate roles... # _peers = [(p._name, p._peer_id) for p in group.get_peers()] - LOG_TRACE(f"update_peer_role group {group!r} has no peer_id={peer_id} in _peers!r") + LOG_TRACE(f"update_peer_role group {group} has no peer_id={peer_id} in _peers!r") # TODO: add info message def remove_peer(group, mod_peer_id, peer_id, is_ban): @@ -638,7 +638,7 @@ def group_moderation(groups_service, contacts_provider, contacts_manager, messen else: # FixMe: known signal to revalidate roles... #_peers = [(p._name, p._peer_id) for p in group.get_peers()] - LOG_TRACE(f"remove_peer group {group!r} has no peer_id={peer_id} in _peers!r") + LOG_TRACE(f"remove_peer group {group} has no peer_id={peer_id} in _peers!r") # TODO: add info message # source_peer_number, target_peer_number, @@ -651,13 +651,13 @@ def group_moderation(groups_service, contacts_provider, contacts_manager, messen mod_peer = group.get_peer_by_id(mod_peer_id) if not mod_peer: #_peers = [(p._name, p._peer_id) for p in group.get_peers()] - LOG_TRACE(f"remove_peer group {group!r} has no mod_peer_id={mod_peer_id} in _peers!r") + LOG_TRACE(f"remove_peer group {group} has no mod_peer_id={mod_peer_id} in _peers!r") return peer = group.get_peer_by_id(peer_id) if not peer: # FixMe: known signal to revalidate roles... #_peers = [(p._name, p._peer_id) for p in group.get_peers()] - LOG_TRACE(f"remove_peer group {group!r} has no peer_id={peer_id} in _peers!r") + LOG_TRACE(f"remove_peer group {group} has no peer_id={peer_id} in _peers!r") return if event_type == TOX_GROUP_MOD_EVENT['KICK']: diff --git a/toxygen/middleware/threads.py b/toxygen/middleware/threads.py index b90bc89..7f0caba 100644 --- a/toxygen/middleware/threads.py +++ b/toxygen/middleware/threads.py @@ -6,8 +6,8 @@ from qtpy import QtCore from bootstrap.bootstrap import * from bootstrap.bootstrap import download_nodes_list -from tox_wrapper.toxcore_enums_and_consts import TOX_USER_STATUS, TOX_CONNECTION -import tox_wrapper.tests.support_testing as ts +from toxygen_wrapper.toxcore_enums_and_consts import TOX_USER_STATUS, TOX_CONNECTION +import toxygen_wrapper.tests.support_testing as ts from utils import util import time @@ -86,7 +86,7 @@ class InitThread(BaseThread): def run(self): # DBUG+ InitThread run: ERROR name 'ts' is not defined - import tox_wrapper.tests.support_testing as ts + import toxygen_wrapper.tests.support_testing as ts LOG_DEBUG('InitThread run: ') try: if self._is_first_start and ts.bAreWeConnected() and \ diff --git a/toxygen/middleware/tox_factory.py b/toxygen/middleware/tox_factory.py index dfb3a1a..1216dd8 100644 --- a/toxygen/middleware/tox_factory.py +++ b/toxygen/middleware/tox_factory.py @@ -6,12 +6,12 @@ import os from ctypes import * import user_data.settings -import tox_wrapper.tox -import tox_wrapper.toxcore_enums_and_consts as enums -from tox_wrapper.tests import support_testing as ts +import toxygen_wrapper.tox +import toxygen_wrapper.toxcore_enums_and_consts as enums +from toxygen_wrapper.tests import support_testing as ts # callbacks can be called in any thread so were being careful # tox.py can be called by callbacks -from tox_wrapper.tests.support_testing import LOG_ERROR, LOG_WARN, LOG_INFO, LOG_DEBUG, LOG_TRACE +from toxygen_wrapper.tests.support_testing import LOG_ERROR, LOG_WARN, LOG_INFO, LOG_DEBUG, LOG_TRACE global LOG import logging @@ -34,7 +34,7 @@ def tox_factory(data=None, settings=None, args=None, app=None): user_data.settings.clean_settings(settings) try: - tox_options = tox_wrapper.tox.Tox.options_new() + tox_options = toxygen_wrapper.tox.Tox.options_new() tox_options.contents.ipv6_enabled = settings['ipv6_enabled'] tox_options.contents.udp_enabled = settings['udp_enabled'] tox_options.contents.proxy_type = int(settings['proxy_type']) @@ -65,7 +65,7 @@ def tox_factory(data=None, settings=None, args=None, app=None): tox_options.contents.ipv6_enabled = False tox_options.contents.hole_punching_enabled = False - LOG.debug("tox_wrapper.tox.Tox settings: " +repr(settings)) + LOG.debug("toxygen_wrapper.tox.Tox settings: " +repr(settings)) if 'trace_enabled' in settings and not settings['trace_enabled']: LOG_DEBUG("settings['trace_enabled' disabled" ) @@ -76,14 +76,14 @@ def tox_factory(data=None, settings=None, args=None, app=None): else: LOG_WARN("No tox_options._options_pointer to add self_logger_cb" ) - retval = tox_wrapper.tox.Tox(tox_options) + retval = toxygen_wrapper.tox.Tox(tox_options) except Exception as e: if app and hasattr(app, '_log'): pass - LOG_ERROR(f"tox_wrapper.tox.Tox failed: {e}") + LOG_ERROR(f"toxygen_wrapper.tox.Tox failed: {e}") LOG_WARN(traceback.format_exc()) raise if app and hasattr(app, '_log'): - app._log("DEBUG: tox_wrapper.tox.Tox succeeded") + app._log("DEBUG: toxygen_wrapper.tox.Tox succeeded") return retval diff --git a/toxygen/notifications/sound.py b/toxygen/notifications/sound.py index 5a65548..fce8e44 100644 --- a/toxygen/notifications/sound.py +++ b/toxygen/notifications/sound.py @@ -2,7 +2,7 @@ import os.path import utils.util import wave -import tox_wrapper.tests.support_testing as ts +import toxygen_wrapper.tests.support_testing as ts with ts.ignoreStderr(): import pyaudio @@ -36,7 +36,7 @@ class AudioFile: self.stream.write(data) data = self.wf.readframes(self.chunk) except Exception as e: - LOG.error(f"Error during AudioFile play {e!s}") + LOG.error(f"Error during AudioFile play {e}") LOG.debug("Error during AudioFile play " \ +' rate=' +str(self.wf.getframerate()) \ + 'format=' +str(self.p.get_format_from_width(self.wf.getsampwidth())) \ diff --git a/toxygen/tests/test_gdb.py b/toxygen/tests/test_gdb.py index 3b1965f..61ecea3 100644 --- a/toxygen/tests/test_gdb.py +++ b/toxygen/tests/test_gdb.py @@ -854,14 +854,14 @@ id(42) cmd = textwrap.dedent(''' class MyList(list): def __init__(self): - super().__init__() # tox_wrapper_call() + super().__init__() # toxygen_wrapper_call() id("first break point") l = MyList() ''') # Verify with "py-bt": gdb_output = self.get_stack_trace(cmd, - cmds_after_breakpoint=['break tox_wrapper_call', 'continue', 'py-bt']) + cmds_after_breakpoint=['break toxygen_wrapper_call', 'continue', 'py-bt']) self.assertRegex(gdb_output, r" +# +# This file is part of QWeeChat, a Qt remote GUI for WeeChat. +# +# QWeeChat is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 3 of the License, or +# (at your option) any later version. +# +# QWeeChat is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with QWeeChat. If not, see . +# + +"""Preferences dialog box.""" + +from qtpy import QtCore, QtGui + + +class PreferencesDialog(QtGui.QDialog): + """Preferences dialog.""" + + def __init__(self, *args): + QtGui.QDialog.__init__(*(self,) + args) + self.setModal(True) + self.setWindowTitle('Preferences') + + close_button = QtGui.QPushButton('Close') + close_button.pressed.connect(self.close) + + hbox = QtGui.QHBoxLayout() + hbox.addStretch(1) + hbox.addWidget(close_button) + hbox.addStretch(1) + + vbox = QtGui.QVBoxLayout() + + label = QtGui.QLabel('Not yet implemented!') + label.setAlignment(QtCore.Qt.AlignHCenter) + vbox.addWidget(label) + + label = QtGui.QLabel('') + label.setAlignment(QtCore.Qt.AlignHCenter) + vbox.addWidget(label) + + vbox.addLayout(hbox) + + self.setLayout(vbox) + self.show() diff --git a/toxygen/third_party/qweechat/qweechat.py.bak b/toxygen/third_party/qweechat/qweechat.py.bak new file mode 100644 index 0000000..465363d --- /dev/null +++ b/toxygen/third_party/qweechat/qweechat.py.bak @@ -0,0 +1,569 @@ +# -*- coding: utf-8 -*- +# +# qweechat.py - WeeChat remote GUI using Qt toolkit +# +# Copyright (C) 2011-2022 Sébastien Helleu +# +# This file is part of QWeeChat, a Qt remote GUI for WeeChat. +# +# QWeeChat is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 3 of the License, or +# (at your option) any later version. +# +# QWeeChat is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with QWeeChat. If not, see . +# + +""" +QWeeChat is a WeeChat remote GUI using Qt toolkit. + +It requires requires WeeChat 0.3.7 or newer, running on local or remote host. +""" + +# +# History: +# +# 2011-05-27, Sébastien Helleu : +# start dev +# + +import sys +import traceback +from pkg_resources import resource_filename + +from qtpy import QtCore, QtGui, QtWidgets + +from qweechat import config +from qweechat.about import AboutDialog +from qweechat.buffer import BufferListWidget, Buffer +from qweechat.connection import ConnectionDialog +from qweechat.network import Network, STATUS_DISCONNECTED +from qweechat.preferences import PreferencesDialog +from qweechat.weechat import protocol + + +APP_NAME = 'QWeeChat' +AUTHOR = 'Sébastien Helleu' +WEECHAT_SITE = 'https://weechat.org/' + +# not QFrame +class MainWindow(QtWidgets.QMainWindow): + """Main window.""" + + def __init__(self, *args): + super().__init__(*args) + + self.config = config.read() + + self.resize(1000, 600) + self.setWindowTitle(APP_NAME) + + self.about_dialog = None + self.connection_dialog = None + self.preferences_dialog = None + + # network + self.network = Network() + self.network.statusChanged.connect(self._network_status_changed) + self.network.messageFromWeechat.connect(self._network_weechat_msg) + + # list of buffers + self.list_buffers = BufferListWidget() + self.list_buffers.currentRowChanged.connect(self._buffer_switch) + + # default buffer + self.buffers = [Buffer()] + self.stacked_buffers = QtWidgets.QStackedWidget() + self.stacked_buffers.addWidget(self.buffers[0].widget) + + # splitter with buffers + chat/input + splitter = QtWidgets.QSplitter() + splitter.addWidget(self.list_buffers) + splitter.addWidget(self.stacked_buffers) + + self.list_buffers.setSizePolicy(QtWidgets.QSizePolicy.Preferred, + QtWidgets.QSizePolicy.Preferred) + self.stacked_buffers.setSizePolicy(QtWidgets.QSizePolicy.Expanding, + QtWidgets.QSizePolicy.Expanding) + # MainWindow + self.setCentralWidget(splitter) + + if self.config.getboolean('look', 'statusbar'): + self.statusBar().visible = True + self.statusBar().visible = True + + # actions for menu and toolbar + actions_def = { + 'connect': [ + 'network-connect.png', + 'Connect to WeeChat', + 'Ctrl+O', + self.open_connection_dialog, + ], + 'disconnect': [ + 'network-disconnect.png', + 'Disconnect from WeeChat', + 'Ctrl+D', + self.network.disconnect_weechat, + ], + 'debug': [ + 'edit-find.png', + 'Open debug console window', + 'Ctrl+B', + self.network.open_debug_dialog, + ], + 'preferences': [ + 'preferences-other.png', + 'Change preferences', + 'Ctrl+P', + self.open_preferences_dialog, + ], + 'about': [ + 'help-about.png', + 'About QWeeChat', + 'Ctrl+H', + self.open_about_dialog, + ], + 'save connection': [ + 'document-save.png', + 'Save connection configuration', + 'Ctrl+S', + self.save_connection, + ], + 'quit': [ + 'application-exit.png', + 'Quit application', + 'Ctrl+Q', + self.close, + ], + } + self.actions = {} + for name, action in list(actions_def.items()): + self.actions[name] = QtWidgets.QAction( + QtGui.QIcon( + resource_filename(__name__, 'data/icons/%s' % action[0])), + name.capitalize(), self) + self.actions[name].setToolTip(f'{action[1]} ({action[2]})') + self.actions[name].setShortcut(action[2]) + self.actions[name].triggered.connect(action[3]) + + # menu + self.menu = self.menuBar() + menu_file = self.menu.addMenu('&File') + menu_file.addActions([self.actions['connect'], + self.actions['disconnect'], + self.actions['preferences'], + self.actions['save connection'], + self.actions['quit']]) + menu_window = self.menu.addMenu('&Window') + menu_window.addAction(self.actions['debug']) + name = 'toggle' + menu_window.addAction( + QtWidgets.QAction(QtGui.QIcon( + resource_filename(__name__, 'data/icons/%s' % 'weechat.png')), + name.capitalize(), self)) + #? .triggered.connect(self.onMyToolBarButtonClick) + + menu_help = self.menu.addMenu('&Help') + menu_help.addAction(self.actions['about']) + self.network_status = QtWidgets.QLabel() + self.network_status.setFixedHeight(20) + self.network_status.setFixedWidth(200) + self.network_status.setContentsMargins(0, 0, 10, 0) + self.network_status.setAlignment(QtCore.Qt.AlignRight) + if hasattr(self.menu, 'setCornerWidget'): + self.menu.setCornerWidget(self.network_status, + QtCore.Qt.TopRightCorner) + self.network_status_set(STATUS_DISCONNECTED) + + # toolbar + toolbar = self.addToolBar('toolBar') + toolbar.setToolButtonStyle(QtCore.Qt.ToolButtonTextUnderIcon) + toolbar.addActions([self.actions['connect'], + self.actions['disconnect'], + self.actions['debug'], + self.actions['preferences'], + self.actions['about'], + self.actions['quit']]) + self.toolbar = toolbar + self.buffers[0].widget.input.setFocus() + + # open debug dialog + if self.config.getboolean('look', 'debug'): + self.network.open_debug_dialog() + + # auto-connect to relay + if self.config.getboolean('relay', 'autoconnect'): + self.network.connect_weechat( + hostname=self.config.get('relay', 'hostname', fallback='127.0.0.1'), + port=self.config.get('relay', 'port', fallback='9000'), + ssl=self.config.getboolean('relay', 'ssl', fallback=False), + password=self.config.get('relay', 'password', fallback=''), + totp=self.config.get('relay', 'password', fallback=''), + lines=self.config.get('relay', 'lines', fallback=''), + ) + + self.show() + + def _buffer_switch(self, index): + """Switch to a buffer.""" + if index >= 0: + self.stacked_buffers.setCurrentIndex(index) + self.stacked_buffers.widget(index).input.setFocus() + + def buffer_input(self, full_name, text): + """Send buffer input to WeeChat.""" + if self.network.is_connected(): + message = 'input %s %s\n' % (full_name, text) + self.network.send_to_weechat(message) + self.network.debug_print(0, '<==', message, forcecolor='#AA0000') + + def open_preferences_dialog(self): + """Open a dialog with preferences.""" + # TODO: implement the preferences dialog box + self.preferences_dialog = PreferencesDialog(self) + + def save_connection(self): + """Save connection configuration.""" + if self.network: + options = self.network.get_options() + for option in options: + self.config.set('relay', option, options[option]) + + def open_about_dialog(self): + """Open a dialog with info about QWeeChat.""" + self.about_dialog = AboutDialog(APP_NAME, AUTHOR, WEECHAT_SITE, self) + + def open_connection_dialog(self): + """Open a dialog with connection settings.""" + values = {} + for option in ('hostname', 'port', 'ssl', 'password', 'lines'): + val = self.config.get('relay', option, fallback='') + if val in [None, 'None']: val = '' + if option == 'port' and val in [None, 'None']: val = 0 + values[option] = val + self.connection_dialog = ConnectionDialog(values, self) + self.connection_dialog.dialog_buttons.accepted.connect( + self.connect_weechat) + + def connect_weechat(self): + """Connect to WeeChat.""" + self.network.connect_weechat( + hostname=self.connection_dialog.fields['hostname'].text(), + port=self.connection_dialog.fields['port'].text(), + ssl=self.connection_dialog.fields['ssl'].isChecked(), + password=self.connection_dialog.fields['password'].text(), + totp=self.connection_dialog.fields['totp'].text(), + lines=int(self.connection_dialog.fields['lines'].text()), + ) + hostname=self.connection_dialog.fields['hostname'].text() + port = self.connection_dialog.fields['port'].text() + ssl=self.connection_dialog.fields['ssl'].isChecked() + password = '' # self.connection_dialog.fields['password'].text() + self.config.set('relay', 'port', port) + self.config.set('relay', 'hostname', hostname) + self.config.set('relay', 'password', password) + self.connection_dialog.close() + + def _network_status_changed(self, status, extra): + """Called when the network status has changed.""" + if self.config.getboolean('look', 'statusbar'): + self.statusBar().showMessage(status) + self.network.debug_print(0, '', status, forcecolor='#0000AA') + self.network_status_set(status) + + def network_status_set(self, status): + """Set the network status.""" + pal = self.network_status.palette() + try: + pal.setColor(self.network_status.foregroundRole(), + self.network.status_color(status)) + except: + # dunno + pass + ssl = ' (SSL)' if status != STATUS_DISCONNECTED \ + and self.network.is_ssl() else '' + self.network_status.setPalette(pal) + icon = self.network.status_icon(status) + if icon: + self.network_status.setText( + ' %s' % + (resource_filename(__name__, 'data/icons/%s' % icon), + self.network.status_label(status) + ssl)) + else: + self.network_status.setText(status.capitalize()) + if status == STATUS_DISCONNECTED: + self.actions['connect'].setEnabled(True) + self.actions['disconnect'].setEnabled(False) + else: + self.actions['connect'].setEnabled(False) + self.actions['disconnect'].setEnabled(True) + + def _network_weechat_msg(self, message): + """Called when a message is received from WeeChat.""" + self.network.debug_print( + 0, '==>', + 'message (%d bytes):\n%s' + % (len(message), + protocol.hex_and_ascii(message.data(), 20)), + forcecolor='#008800', + ) + try: + proto = protocol.Protocol() + message = proto.decode(message.data()) + if message.uncompressed: + self.network.debug_print( + 0, '==>', + 'message uncompressed (%d bytes):\n%s' + % (message.size_uncompressed, + protocol.hex_and_ascii(message.uncompressed, 20)), + forcecolor='#008800') + self.network.debug_print(0, '', 'Message: %s' % message) + self.parse_message(message) + except Exception: # noqa: E722 + print('Error while decoding message from WeeChat:\n%s' + % traceback.format_exc()) + self.network.disconnect_weechat() + + def _parse_handshake(self, message): + """Parse a WeeChat message with handshake response.""" + for obj in message.objects: + if obj.objtype != 'htb': + continue + self.network.init_with_handshake(obj.value) + break + + def _parse_listbuffers(self, message): + """Parse a WeeChat message with list of buffers.""" + for obj in message.objects: + if obj.objtype != 'hda' or obj.value['path'][-1] != 'buffer': + continue + self.list_buffers.clear() + while self.stacked_buffers.count() > 0: + buf = self.stacked_buffers.widget(0) + self.stacked_buffers.removeWidget(buf) + self.buffers = [] + for item in obj.value['items']: + buf = self.create_buffer(item) + self.insert_buffer(len(self.buffers), buf) + self.list_buffers.setCurrentRow(0) + self.buffers[0].widget.input.setFocus() + + def _parse_line(self, message): + """Parse a WeeChat message with a buffer line.""" + for obj in message.objects: + lines = [] + if obj.objtype != 'hda' or obj.value['path'][-1] != 'line_data': + continue + for item in obj.value['items']: + if message.msgid == 'listlines': + ptrbuf = item['__path'][0] + else: + ptrbuf = item['buffer'] + index = [i for i, b in enumerate(self.buffers) + if b.pointer() == ptrbuf] + if index: + lines.append( + (index[0], + (item['date'], item['prefix'], + item['message'])) + ) + if message.msgid == 'listlines': + lines.reverse() + for line in lines: + self.buffers[line[0]].widget.chat.display(*line[1]) + + def _parse_nicklist(self, message): + """Parse a WeeChat message with a buffer nicklist.""" + buffer_refresh = {} + for obj in message.objects: + if obj.objtype != 'hda' or \ + obj.value['path'][-1] != 'nicklist_item': + continue + group = '__root' + for item in obj.value['items']: + index = [i for i, b in enumerate(self.buffers) + if b.pointer() == item['__path'][0]] + if index: + if not index[0] in buffer_refresh: + self.buffers[index[0]].nicklist = {} + buffer_refresh[index[0]] = True + if item['group']: + group = item['name'] + self.buffers[index[0]].nicklist_add_item( + group, item['group'], item['prefix'], item['name'], + item['visible']) + for index in buffer_refresh: + self.buffers[index].nicklist_refresh() + + def _parse_nicklist_diff(self, message): + """Parse a WeeChat message with a buffer nicklist diff.""" + buffer_refresh = {} + for obj in message.objects: + if obj.objtype != 'hda' or \ + obj.value['path'][-1] != 'nicklist_item': + continue + group = '__root' + for item in obj.value['items']: + index = [i for i, b in enumerate(self.buffers) + if b.pointer() == item['__path'][0]] + if not index: + continue + buffer_refresh[index[0]] = True + if item['_diff'] == ord('^'): + group = item['name'] + elif item['_diff'] == ord('+'): + self.buffers[index[0]].nicklist_add_item( + group, item['group'], item['prefix'], item['name'], + item['visible']) + elif item['_diff'] == ord('-'): + self.buffers[index[0]].nicklist_remove_item( + group, item['group'], item['name']) + elif item['_diff'] == ord('*'): + self.buffers[index[0]].nicklist_update_item( + group, item['group'], item['prefix'], item['name'], + item['visible']) + for index in buffer_refresh: + self.buffers[index].nicklist_refresh() + + def _parse_buffer_opened(self, message): + """Parse a WeeChat message with a new buffer (opened).""" + for obj in message.objects: + if obj.objtype != 'hda' or obj.value['path'][-1] != 'buffer': + continue + for item in obj.value['items']: + buf = self.create_buffer(item) + index = self.find_buffer_index_for_insert(item['next_buffer']) + self.insert_buffer(index, buf) + + def _parse_buffer(self, message): + """Parse a WeeChat message with a buffer event + (anything except a new buffer). + """ + for obj in message.objects: + if obj.objtype != 'hda' or obj.value['path'][-1] != 'buffer': + continue + for item in obj.value['items']: + index = [i for i, b in enumerate(self.buffers) + if b.pointer() == item['__path'][0]] + if not index: + continue + index = index[0] + if message.msgid == '_buffer_type_changed': + self.buffers[index].data['type'] = item['type'] + elif message.msgid in ('_buffer_moved', '_buffer_merged', + '_buffer_unmerged'): + buf = self.buffers[index] + buf.data['number'] = item['number'] + self.remove_buffer(index) + index2 = self.find_buffer_index_for_insert( + item['next_buffer']) + self.insert_buffer(index2, buf) + elif message.msgid == '_buffer_renamed': + self.buffers[index].data['full_name'] = item['full_name'] + self.buffers[index].data['short_name'] = item['short_name'] + elif message.msgid == '_buffer_title_changed': + self.buffers[index].data['title'] = item['title'] + self.buffers[index].update_title() + elif message.msgid == '_buffer_cleared': + self.buffers[index].widget.chat.clear() + elif message.msgid.startswith('_buffer_localvar_'): + self.buffers[index].data['local_variables'] = \ + item['local_variables'] + self.buffers[index].update_prompt() + elif message.msgid == '_buffer_closing': + self.remove_buffer(index) + + def parse_message(self, message): + """Parse a WeeChat message.""" + if message.msgid.startswith('debug'): + self.network.debug_print(0, '', '(debug message, ignored)') + elif message.msgid == 'handshake': + self._parse_handshake(message) + elif message.msgid == 'listbuffers': + self._parse_listbuffers(message) + elif message.msgid in ('listlines', '_buffer_line_added'): + self._parse_line(message) + elif message.msgid in ('_nicklist', 'nicklist'): + self._parse_nicklist(message) + elif message.msgid == '_nicklist_diff': + self._parse_nicklist_diff(message) + elif message.msgid == '_buffer_opened': + self._parse_buffer_opened(message) + elif message.msgid.startswith('_buffer_'): + self._parse_buffer(message) + elif message.msgid == '_upgrade': + self.network.desync_weechat() + elif message.msgid == '_upgrade_ended': + self.network.sync_weechat() + else: + print(f"Unknown message with id {message.msgid}") + + def create_buffer(self, item): + """Create a new buffer.""" + buf = Buffer(item) + buf.bufferInput.connect(self.buffer_input) + buf.widget.input.bufferSwitchPrev.connect( + self.list_buffers.switch_prev_buffer) + buf.widget.input.bufferSwitchNext.connect( + self.list_buffers.switch_next_buffer) + return buf + + def insert_buffer(self, index, buf): + """Insert a buffer in list.""" + self.buffers.insert(index, buf) + self.list_buffers.insertItem(index, '%s' + % (buf.data['local_variables']['name'])) + self.stacked_buffers.insertWidget(index, buf.widget) + + def remove_buffer(self, index): + """Remove a buffer.""" + if self.list_buffers.currentRow == index and index > 0: + self.list_buffers.setCurrentRow(index - 1) + self.list_buffers.takeItem(index) + self.stacked_buffers.removeWidget(self.stacked_buffers.widget(index)) + self.buffers.pop(index) + + def find_buffer_index_for_insert(self, next_buffer): + """Find position to insert a buffer in list.""" + index = -1 + if next_buffer == '0x0': + index = len(self.buffers) + else: + elts = [i for i, b in enumerate(self.buffers) + if b.pointer() == next_buffer] + if len(elts): + index = elts[0] + if index < 0: + print('Warning: unable to find position for buffer, using end of ' + 'list by default') + index = len(self.buffers) + return index + + def closeEvent(self, event): + """Called when QWeeChat window is closed.""" + self.network.disconnect_weechat() + if self.network.debug_dialog: + self.network.debug_dialog.close() + config.write(self.config) + QtWidgets.QFrame.closeEvent(self, event) + + +def main(): + app = QtWidgets.QApplication(sys.argv) + app.setStyle(QtWidgets.QStyleFactory.create('Cleanlooks')) + app.setWindowIcon(QtGui.QIcon( + resource_filename(__name__, 'data/icons/weechat.png'))) + main_win = MainWindow() + main_win.show() + sys.exit(app.exec_()) + + +if __name__ == '__main__': + main() diff --git a/toxygen/ui/av_widgets.py b/toxygen/ui/av_widgets.py index 2d90431..ba3e116 100644 --- a/toxygen/ui/av_widgets.py +++ b/toxygen/ui/av_widgets.py @@ -5,7 +5,7 @@ import wave from ui import widgets import utils.util as util -import tox_wrapper.tests.support_testing as ts +import toxygen_wrapper.tests.support_testing as ts with ts.ignoreStderr(): import pyaudio @@ -118,6 +118,7 @@ class IncomingCallWidget(widgets.CenteredWidget): self.thread = None def stop(self): + LOG.debug(f"stop from {self._friend_number}") if self._processing: self.close() if self.thread is not None: @@ -128,8 +129,7 @@ class IncomingCallWidget(widgets.CenteredWidget): if not self.thread.isRunning(): break i = i + 1 else: - LOG.warn(f"SoundPlay {self.thread.a} BLOCKED") - # Fatal Python error: Segmentation fault + LOG.warn(f"stop {self.thread.a} BLOCKED") self.thread.a.stream.close() self.thread.a.p.terminate() self.thread.a.close() @@ -153,7 +153,7 @@ class IncomingCallWidget(widgets.CenteredWidget): # ts.trepan_handler() if self._processing: - LOG.warn(__name__+f" accept_call_with_video from {self._friend_number}") + LOG.warn(f" accept_call_with_video from {self._friend_number}") return self.setWindowTitle('Answering video call') self._processing = True @@ -164,11 +164,14 @@ class IncomingCallWidget(widgets.CenteredWidget): self.stop() def decline_call(self): + LOG.debug(f"decline_call from {self._friend_number}") if self._processing: return self._processing = True try: self._calls_manager.stop_call(self._friend_number, False) + except Exception as e: + LOG.warn(f"decline_call from {self._friend_number} {e}") finally: self.stop() diff --git a/toxygen/ui/contact_items.py b/toxygen/ui/contact_items.py index db66c2a..8938c3b 100644 --- a/toxygen/ui/contact_items.py +++ b/toxygen/ui/contact_items.py @@ -1,4 +1,4 @@ -from tox_wrapper.toxcore_enums_and_consts import * +from toxygen_wrapper.toxcore_enums_and_consts import * from qtpy import QtCore, QtGui, QtWidgets from utils.util import * from ui.widgets import DataLabel diff --git a/toxygen/ui/group_peers_list.py b/toxygen/ui/group_peers_list.py index fc24533..f72bc62 100644 --- a/toxygen/ui/group_peers_list.py +++ b/toxygen/ui/group_peers_list.py @@ -1,5 +1,5 @@ from ui.widgets import * -from tox_wrapper.toxcore_enums_and_consts import * +from toxygen_wrapper.toxcore_enums_and_consts import * class PeerItem(QtWidgets.QWidget): diff --git a/toxygen/ui/groups_widgets.py b/toxygen/ui/groups_widgets.py index 29319b3..3fb01ab 100644 --- a/toxygen/ui/groups_widgets.py +++ b/toxygen/ui/groups_widgets.py @@ -1,7 +1,7 @@ from qtpy import uic import utils.util as util from ui.widgets import * -from tox_wrapper.toxcore_enums_and_consts import * +from toxygen_wrapper.toxcore_enums_and_consts import * class BaseGroupScreen(CenteredWidget): diff --git a/toxygen/ui/menu.py b/toxygen/ui/menu.py index a12f94d..27052a6 100644 --- a/toxygen/ui/menu.py +++ b/toxygen/ui/menu.py @@ -1,7 +1,7 @@ # -*- mode: python; indent-tabs-mode: nil; py-indent-offset: 4; coding: utf-8 -*- from qtpy import QtCore, QtGui, QtWidgets, uic -import tox_wrapper.tests.support_testing as ts +import toxygen_wrapper.tests.support_testing as ts with ts.ignoreStderr(): # not out import pyaudio @@ -504,7 +504,7 @@ class AudioSettings(CenteredWidget): def closeEvent(self, event): if 'audio' not in self._settings: - ex = f"self._settings=id(self._settings) {self._settings!r}" + ex = f"self._settings=id(self._settings) {self._settings}" LOG.warn('AudioSettings.closeEvent settings error: ' + str(ex)) else: self._settings['audio']['input'] = \ @@ -574,7 +574,7 @@ class VideoSettings(CenteredWidget): if index in self._devices: self._settings['video']['device'] = self._devices[index] else: - LOG.warn(f"{index} not in deviceComboBox self._devices {self._devices!r}") + LOG.warn(f"{index} not in deviceComboBox self._devices {self._devices}") text = self.resolutionComboBox.currentText() if len(text.split(' ')[0]) > 1: self._settings['video']['width'] = int(text.split(' ')[0]) @@ -621,7 +621,7 @@ class VideoSettings(CenteredWidget): self.deviceComboBox.addItem(util_ui.tr('Device #') + str(i)) if 'device' not in self._settings['video']: - LOG.warn(f"'device' not in self._settings['video']: {self._settings!r}") + LOG.warn(f"'device' not in self._settings['video']: {self._settings}") self._settings['video']['device'] = self._devices[-1] iIndex = self._settings['video']['device'] try: diff --git a/toxygen/ui/messages_widgets.py b/toxygen/ui/messages_widgets.py index a89b7ff..0c71511 100644 --- a/toxygen/ui/messages_widgets.py +++ b/toxygen/ui/messages_widgets.py @@ -1,4 +1,4 @@ -from tox_wrapper.toxcore_enums_and_consts import * +from toxygen_wrapper.toxcore_enums_and_consts import * import ui.widgets as widgets import utils.util as util import ui.menu as menu diff --git a/toxygen/ui/peer_screen.py b/toxygen/ui/peer_screen.py index 30986b6..b5d0a9e 100644 --- a/toxygen/ui/peer_screen.py +++ b/toxygen/ui/peer_screen.py @@ -3,7 +3,7 @@ from qtpy import uic import utils.util as util import utils.ui as util_ui from ui.contact_items import * -import tox_wrapper.toxcore_enums_and_consts as consts +import toxygen_wrapper.toxcore_enums_and_consts as consts class PeerScreen(CenteredWidget):