diff --git a/toxygen/av/calls.py b/toxygen/av/calls.py index 26bf055..2ef613d 100644 --- a/toxygen/av/calls.py +++ b/toxygen/av/calls.py @@ -16,26 +16,51 @@ import common.tox_save from utils import ui as util_ui import toxygen_wrapper.tests.support_testing as ts from middleware.threads import invoke_in_main_thread -from middleware.threads import BaseThread +from middleware.threads import BaseThread, BaseQThread - -from qtpy import QtCore -# sleep = time.sleep -def sleep(fSec): - mSec = int(fSec * 1000.0) - QtCore.QThread.msleep(mSec) - # GLib:ERROR:../glib-2.78.4/glib/gmain.c:3428:g_main_dispatch: assertion failed: (source) - # Bail out! GLib:ERROR:../glib-2.78.4/glib/gmain.c:3428:g_main_dispatch: assertion failed: (source) - QtCore.QCoreApplication.processEvents() +sleep = time.sleep global LOG import logging LOG = logging.getLogger('app.'+__name__) TIMER_TIMEOUT = 30.0 -bSTREAM_CALLBACK = False iFPS = 25 +class AudioThread(BaseQThread): + def __init__(self, av, name=''): + super().__init__() + self.av = av + self._name = name + + def join(self, ito=ts.iTHREAD_TIMEOUT): + LOG_DEBUG(f"AudioThread join {self}") + # dunno + + def run(self): + LOG_DEBUG('AudioThread run: ') + # maybe not needed + while not self._stop_thread: + self.av.send_audio() + sleep(100.0 / 1000.0) + +class VideoThread(BaseQThread): + def __init__(self, av, name=''): + super().__init__() + self.av = av + self._name = name + + def join(self, ito=ts.iTHREAD_TIMEOUT): + LOG_DEBUG(f"VideoThread join {self}") + # dunno + + def run(self): + LOG_DEBUG('VideoThread run: ') + # maybe not needed + while not self._stop_thread: + self.av.send_video() + sleep(100.0 / 1000.0) + class AV(common.tox_save.ToxAvSave): def __init__(self, toxav, settings): @@ -81,6 +106,7 @@ class AV(common.tox_save.ToxAvSave): self.lPaSampleratesI = ts.lSdSamplerates(iInput) iOutput = self._settings['audio']['output'] self.lPaSampleratesO = ts.lSdSamplerates(iOutput) + global oPYA oPYA = self._audio = pyaudio.PyAudio() @@ -119,8 +145,9 @@ class AV(common.tox_save.ToxAvSave): return self.call_accept_call(friend_number, audio_enabled, video_enabled) def call_accept_call(self, friend_number, audio_enabled, video_enabled): - LOG.debug(f"call_accept_call from {friend_number} {self._running}" + - f"{audio_enabled} {video_enabled}") + # called from CM.accept_call in a try: + LOG.debug(f"call_accept_call from F={friend_number} R={self._running}" + + f" A={audio_enabled} V={video_enabled}") # import pdb; pdb.set_trace() - gets into q Qt exec_ problem # ts.trepan_handler() @@ -135,14 +162,15 @@ 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} {e}") + LOG.error(f"AV accept_call error from {friend_number} {self._running} {e}") raise - if audio_enabled: - # may raise - self.start_audio_thread() if video_enabled: # may raise self.start_video_thread() + if audio_enabled: + LOG.debug(f"calls accept_call calling start_audio_thread F={friend_number}") + # may raise + self.start_audio_thread() def finish_call(self, friend_number, by_friend=False): LOG.debug(f"finish_call {friend_number}") @@ -192,11 +220,12 @@ class AV(common.tox_save.ToxAvSave): # Threads - def start_audio_thread(self): + def start_audio_thread(self, bSTREAM_CALLBACK=False): """ Start audio sending from a callback """ + # called from call_accept_call in an try: from CM.accept_call global oPYA # was iInput = self._settings._args.audio['input'] iInput = self._settings['audio']['input'] @@ -207,35 +236,37 @@ class AV(common.tox_save.ToxAvSave): lPaSamplerates = ts.lSdSamplerates(iInput) if not(len(lPaSamplerates)): e = f"No sample rates for device: audio[input]={iInput}" - LOG_ERROR(f"start_audio_thread {e}") + LOG_WARN(f"start_audio_thread {e}") #?? dunno - cancel call? - no let the user do it # return # just guessing here in case that's a false negative - lPaSamplerates = [round(self._audio.get_device_info_by_index(iInput)['defaultSampleRate'])] + 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 self._audio.get_device_info_by_index(iInput): - self._audio_rate_pa = self._audio.get_device_info_by_index(iInput)['defaultSampleRate'] + self._audio_rate_pa = lPaSamplerates[0] + elif 'defaultSampleRate' in oPYA.get_device_info_by_index(iInput): + self._audio_rate_pa = oPYA.get_device_info_by_index(iInput)['defaultSampleRate'] + LOG_WARN(f"setting to 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}") 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] + else: + LOG_DEBUG( f"start_audio_thread framerate: {self._audio_rate_pa}" \ + +f" device: {iInput}" + +f" supported: {lPaSamplerates}") if bSTREAM_CALLBACK: - self._audio_stream = self._audio.open(format=pyaudio.paInt16, + # why would you not call a thread? + self._audio_stream = oPYA.open(format=pyaudio.paInt16, rate=self._audio_rate_pa, channels=self._audio_channels, input=True, @@ -248,30 +279,33 @@ class AV(common.tox_save.ToxAvSave): sleep(0.1) self._audio_stream.stop_stream() self._audio_stream.close() - else: - self._audio_stream = self._audio.open(format=pyaudio.paInt16, + LOG_DEBUG( f"start_audio_thread starting thread {self._audio_rate_pa}") + self._audio_stream = oPYA.open(format=pyaudio.paInt16, rate=self._audio_rate_pa, channels=self._audio_channels, input=True, input_device_index=iInput, frames_per_buffer=self._audio_sample_count_pa * 10) self._audio_running = True - self._audio_thread = BaseThread(target=self.send_audio, - name='_audio_thread') + self._audio_thread = AudioThread(self, + name='_audio_thread') self._audio_thread.start() + LOG_DEBUG( f"start_audio_thread started thread name='_audio_thread'") except Exception as e: - LOG.error(f"Starting self._audio.open {e}") - LOG.debug(repr(dict(format=pyaudio.paInt16, + LOG_ERROR(f"Starting self._audio.open {e}") + LOG_DEBUG(repr(dict(format=pyaudio.paInt16, rate=self._audio_rate_pa, channels=self._audio_channels, input=True, input_device_index=iInput, frames_per_buffer=self._audio_sample_count_pa * 10))) - # catcher in place in calls_manager? not if from a callback + # catcher in place in calls_manager? yes accept_call # calls_manager._call.toxav_call_state_cb(friend_number, mask) - # raise RuntimeError(e) + invoke_in_main_thread(util_ui.message_box, + str(e), + util_ui.tr("Starting self._audio.open")) return else: LOG_DEBUG(f"start_audio_thread {self._audio_stream}") @@ -282,6 +316,7 @@ class AV(common.tox_save.ToxAvSave): if self._audio_thread is None: return self._audio_running = False + self._audio_thread._stop_thread = True self._audio_thread = None self._audio_stream = None @@ -334,7 +369,7 @@ class AV(common.tox_save.ToxAvSave): +f" supported: {s['video']['width']} {s['video']['height']}") self._video_running = True - self._video_thread = BaseThread(target=self.send_video, + self._video_thread = VideoThread(self, name='_video_thread') self._video_thread.start() @@ -343,6 +378,7 @@ class AV(common.tox_save.ToxAvSave): if self._video_thread is None: return + self._video_thread._stop_thread = True self._video_running = False i = 0 while i < ts.iTHREAD_JOINS: @@ -356,80 +392,86 @@ class AV(common.tox_save.ToxAvSave): LOG.warn("self._video_thread.is_alive BLOCKED") self._video_thread = None self._video = None - # Incoming chunks def audio_chunk(self, samples, channels_count, rate) -> None: """ Incoming chunk """ - + # from callback if self._out_stream is None: # was iOutput = self._settings._args.audio['output'] iOutput = self._settings['audio']['output'] if self.lPaSampleratesO and rate in self.lPaSampleratesO: - pass + LOG_DEBUG(f"Using rate {rate} in self.lPaSampleratesO") elif self.lPaSampleratesO: - LOG.warn(f"{rate} not in {self.lPaSampleratesO}") - LOG.warn(f"Setting audio_rate to: {self.lPaSampleratesO[0]}") + 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 self._audio.get_device_info_by_index(iOutput): - rate = round(self._audio.get_device_info_by_index(iOutput)['defaultSampleRate']) - LOG.warn(f"Setting rate to {rate} empty self.lPaSampleratesO") + 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") + LOG_WARN(f"Using rate {rate} empty self.lPaSampleratesO") if type(rate) == float: rate = round(rate) # test output device? # [Errno -9985] Device unavailable try: with ts.ignoreStderr(): - self._out_stream = self._audio.open(format=pyaudio.paInt16, + self._out_stream = oPYA.open(format=pyaudio.paInt16, channels=channels_count, rate=rate, output_device_index=iOutput, output=True) except Exception as e: - LOG.error(f"Error playing audio_chunk creating self._out_stream output_device_index={iOutput} {e}") + LOG_ERROR(f"Error playing audio_chunk creating self._out_stream output_device_index={iOutput} {e}") invoke_in_main_thread(util_ui.message_box, - str(e), - util_ui.tr("Error Chunking audio")) + str(e), + util_ui.tr("Error Chunking audio")) # dunno self.stop() return iOutput = self._settings['audio']['output'] -#trace LOG.debug(f"audio_chunk output_device_index={iOutput} rate={rate} channels={channels_count}") - self._out_stream.write(samples) +#trace LOG_DEBUG(f"audio_chunk output_device_index={iOutput} rate={rate} channels={channels_count}") + try: + self._out_stream.write(samples) + except Exception as e: + # OSError: [Errno -9999] Unanticipated host error + LOG_WARN(f"audio_chunk output_device_index={iOutput} {e}") # AV sending def send_audio_data(self, data, count, *largs, **kwargs) -> None: + # callback pcm = data # :param sampling_rate: Audio sampling rate used in this frame. - if self._toxav is None: - raise RuntimeError("_toxav not initialized") - if self._audio_rate_tox not in ts.lToxSamplerates: - LOG.warn(f"ToxAudio sampling rate was {self._audio_rate_tox} changed to {ts.lToxSamplerates[0]}") - self._audio_rate_tox = ts.lToxSamplerates[0] + try: + if self._toxav is None: + LOG_ERROR("_toxav not initialized") + return + if self._audio_rate_tox not in ts.lToxSamplerates: + LOG_WARN(f"ToxAudio sampling rate was {self._audio_rate_tox} changed to {ts.lToxSamplerates[0]}") + self._audio_rate_tox = ts.lToxSamplerates[0] - for friend_num in self._calls: - if self._calls[friend_num].out_audio: - try: + for friend_num in self._calls: + if self._calls[friend_num].out_audio: # 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, count, self._audio_channels, self._audio_rate_tox) - except Exception as e: - LOG.error(f"Error send_audio audio_send_frame: {e}") - LOG.debug(f"send_audio self._audio_rate_tox={self._audio_rate_tox} self._audio_channels={self._audio_channels}") -# invoke_in_main_thread(util_ui.message_box, -# str(e), -# util_ui.tr("Error send_audio audio_send_frame")) - # raise #? stop ? endcall? - self.stop_audio_thread() +ts) + except Exception as e: + LOG.error(f"Error send_audio_data audio_send_frame: {e}") + LOG.debug(f"send_audio_data self._audio_rate_tox={self._audio_rate_tox} self._audio_channels={self._audio_channels}") + self.stop_audio_thread() + invoke_in_main_thread(util_ui.message_box, + str(e), + util_ui.tr("Error send_audio_data audio_send_frame")) + #? stop ? endcall? def send_audio(self) -> None: """ @@ -437,7 +479,7 @@ class AV(common.tox_save.ToxAvSave): """ i=0 count = self._audio_sample_count_tox - LOG.debug(f"send_audio stream={self._audio_stream}") + LOG_DEBUG(f"send_audio stream={self._audio_stream}") while self._audio_running: try: pcm = self._audio_stream.read(count, exception_on_overflow=False) @@ -456,17 +498,17 @@ class AV(common.tox_save.ToxAvSave): """ This method sends video to friends """ - LOG.debug(f"send_video thread={threading.current_thread().name}" - +f" self._video_running={self._video_running}" - +f" device: {self._settings['video']['device']}" ) +# LOG_DEBUG(f"send_video thread={threading.current_thread().name}" +# +f" self._video_running={self._video_running}" +# +f" device: {self._settings['video']['device']}" ) while self._video_running: try: result, frame = self._video.read() if not result: - LOG.warn(f"send_video video_send_frame _video.read result={result}") + LOG_WARN(f"send_video video_send_frame _video.read result={result}") break if frame is None: - LOG.warn(f"send_video video_send_frame _video.read result={result} frame={frame}") + LOG_WARN(f"send_video video_send_frame _video.read result={result} frame={frame}") continue else: LOG_TRACE(f"send_video video_send_frame _video.read result={result}") @@ -476,7 +518,7 @@ class AV(common.tox_save.ToxAvSave): if self._calls[friend_num].out_video: friends.append(friend_num) if len(friends) == 0: - LOG.warn(f"send_video video_send_frame no friends") + LOG_WARN(f"send_video video_send_frame no friends") else: LOG_TRACE(f"send_video video_send_frame {friends}") friend_num = friends[0] @@ -484,11 +526,11 @@ class AV(common.tox_save.ToxAvSave): y, u, v = self.convert_bgr_to_yuv(frame) self._toxav.video_send_frame(friend_num, width, height, y, u, v) except Exception as e: - LOG.debug(f"send_video video_send_frame ERROR {e}") + LOG_WARN(f"send_video video_send_frame ERROR {e}") pass except Exception as e: - LOG.error(f"send_video video_send_frame {e}") + LOG_ERROR(f"send_video video_send_frame {e}") pass sleep( 1.0/iFPS) @@ -532,11 +574,12 @@ class AV(common.tox_save.ToxAvSave): y = list(itertools.chain.from_iterable(y)) import numpy as np - u = np.zeros((self._video_height // 2, self._video_width // 2), dtype=np.int) + # was np.int + u = np.zeros((self._video_height // 2, self._video_width // 2), dtype=np.int32) 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 = np.zeros((self._video_height // 2, self._video_width // 2), dtype=np.int32) 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)) diff --git a/toxygen/av/calls_manager.py b/toxygen/av/calls_manager.py index 537efae..aedcd19 100644 --- a/toxygen/av/calls_manager.py +++ b/toxygen/av/calls_manager.py @@ -3,6 +3,9 @@ import sys import threading import traceback +import logging + +from qtpy import QtCore import av.calls from messenger.messages import * @@ -11,7 +14,6 @@ import common.event as event import utils.ui as util_ui global LOG -import logging LOG = logging.getLogger('app.'+__name__) class CallsManager: @@ -63,7 +65,7 @@ class CallsManager: """ Incoming call from friend. """ - LOG.debug(__name__ +f" incoming_call {friend_number}") + LOG.debug(f"CM incoming_call {friend_number}") # if not self._settings['audio']['enabled']: return friend = self._contacts_manager.get_friend_by_number(friend_number) self._call_started_event(friend_number, audio, video, False) @@ -83,11 +85,17 @@ class CallsManager: Called from a thread """ - LOG.debug(f"CM accept_call from {friend_number} {audio} {video}") + LOG.debug(f"CM accept_call from friend_number={friend_number} {audio} {video}") sys.stdout.flush() try: + self._main_screen.active_call() + # failsafe added somewhere this was being left up + self.close_call(friend_number) + QtCore.QCoreApplication.processEvents() + self._callav.call_accept_call(friend_number, audio, video) + LOG.debug(f"accept_call _call.accept_call CALLED f={friend_number}") except Exception as e: # LOG.error(f"accept_call _call.accept_call ERROR for {friend_number} {e}") @@ -112,22 +120,29 @@ class CallsManager: LOG.warn(f"_settings not in self._main_screen") util_ui.message_box(str(e), util_ui.tr('ERROR Accepting call from {friend_number}')) - else: - self._main_screen.active_call() finally: # does not terminate call - just the av_widget LOG.debug(f"CM.accept_call close av_widget") - try: + self.close_call(friend_number) + LOG.debug(f" closed self._call_widgets[{friend_number}]") + + def close_call(self, friend_number): + # refactored out from above because the accept window not getting + # taken down in some accept audio calls + LOG.debug(f"close_call {friend_number}") + try: + if friend_number in self._call_widgets: self._call_widgets[friend_number].close() del self._call_widgets[friend_number] - if friend_number in self._incoming_calls: - self._incoming_calls.remove(friend_number) - except Exception as e: - # RuntimeError: wrapped C/C++ object of type IncomingCallWidget has been deleted + if friend_number in self._incoming_calls: + self._incoming_calls.remove(friend_number) + except Exception as e: + # RuntimeError: wrapped C/C++ object of type IncomingCallWidget has been deleted - LOG.warn(f" closed self._call_widgets[{friend_number}] {e}") + LOG.warn(f" closed self._call_widgets[{friend_number}] {e}") + # invoke_in_main_thread(QtCore.QCoreApplication.processEvents) + QtCore.QCoreApplication.processEvents() - LOG.debug(f" closed self._call_widgets[{friend_number}]") def stop_call(self, friend_number, by_friend): """ @@ -141,8 +156,7 @@ class CallsManager: is_declined = False if friend_number in self._call_widgets: LOG.debug(f"CM.stop_call _call_widgets close") - self._call_widgets[friend_number].close() - del self._call_widgets[friend_number] + self.close_call(friend_number) LOG.debug(f"CM.stop_call _main_screen.call_finished") self._main_screen.call_finished() diff --git a/toxygen/file_transfers/file_transfers_handler.py b/toxygen/file_transfers/file_transfers_handler.py index b77c6f0..557746e 100644 --- a/toxygen/file_transfers/file_transfers_handler.py +++ b/toxygen/file_transfers/file_transfers_handler.py @@ -93,6 +93,7 @@ class FileTransfersHandler(ToxSave): :param file_number: file number :param already_cancelled: was cancelled by friend """ + # callback if (friend_number, file_number) in self._file_transfers: tr = self._file_transfers[(friend_number, file_number)] if not already_cancelled: diff --git a/toxygen/history/database.py b/toxygen/history/database.py index 130572e..7d8dd35 100644 --- a/toxygen/history/database.py +++ b/toxygen/history/database.py @@ -5,7 +5,7 @@ import utils.util as util global LOG import logging -LOG = logging.getLogger('app.db') +LOG = logging.getLogger('h.database') TIMEOUT = 11 SAVE_MESSAGES = 500 @@ -86,7 +86,7 @@ class Database: db.commit() return True except Exception as e: - LOG.error("dd_friend_to_db " +self._name +' Database exception! ' +str(e)) + LOG.error("dd_friend_to_db " +self._name +f" Database exception! {e}") db.rollback() return False finally: @@ -101,7 +101,7 @@ class Database: db.commit() return True except Exception as e: - LOG.error("delete_friend_from_db " +self._name +' Database exception! ' +str(e)) + LOG.error("delete_friend_from_db " +self._name +f" Database exception! {e}") db.rollback() return False finally: @@ -118,7 +118,7 @@ class Database: db.commit() return True except Exception as e: - LOG.error("" +self._name +' Database exception! ' +str(e)) + LOG.error("save_messages_to_db" +self._name +f" Database exception! {e}") db.rollback() return False finally: @@ -134,7 +134,7 @@ class Database: db.commit() return True except Exception as e: - LOG.error("" +self._name +' Database exception! ' +str(e)) + LOG.error("update_messages" +self._name +f" Database exception! {e}") db.rollback() return False finally: @@ -149,7 +149,7 @@ class Database: db.commit() return True except Exception as e: - LOG.error("" +self._name +' Database exception! ' +str(e)) + LOG.error("delete_message" +self._name +f" Database exception! {e}") db.rollback() return False finally: @@ -164,7 +164,7 @@ class Database: db.commit() return True except Exception as e: - LOG.error("" +self._name +' Database exception! ' +str(e)) + LOG.error("delete_messages" +self._name +f" Database exception! {e}") db.rollback() return False finally: diff --git a/toxygen/middleware/callbacks.py b/toxygen/middleware/callbacks.py index 3354d1b..0c1e0a2 100644 --- a/toxygen/middleware/callbacks.py +++ b/toxygen/middleware/callbacks.py @@ -426,7 +426,7 @@ def video_receive_frame(toxav, friend_number, width, height, y, u, v, ystride, u frame[height * 5 // 4:, width // 2:] = v[1:height // 2:2, :width // 2] frame = cv2.cvtColor(frame, cv2.COLOR_YUV2BGR_I420) - + # imshow invoke_in_main_thread(cv2.imshow, str(friend_number), frame) except Exception as ex: LOG_ERROR(f"video_receive_frame {ex} #{friend_number}") diff --git a/toxygen/middleware/threads.py b/toxygen/middleware/threads.py index 7f0caba..75e3fc9 100644 --- a/toxygen/middleware/threads.py +++ b/toxygen/middleware/threads.py @@ -23,7 +23,7 @@ def LOG_ERROR(l): print('EROR+ '+l) def LOG_WARN(l): print('WARN+ '+l) def LOG_INFO(l): print('INFO+ '+l) def LOG_DEBUG(l): print('DBUG+ '+l) -def LOG_TRACE(l): pass # print('TRACE+ '+l) +def LOG_TRACE(l): pass # print('TRAC+ '+l) iLAST_CONN = 0 iLAST_DELTA = 60 diff --git a/toxygen/ui/main_screen.py b/toxygen/ui/main_screen.py index a8ec855..27bbbeb 100644 --- a/toxygen/ui/main_screen.py +++ b/toxygen/ui/main_screen.py @@ -704,6 +704,7 @@ class MainWindow(QtWidgets.QMainWindow): return try: from qweechat import qweechat + from qweechat.config import write LOG.info("Loading WeechatConsole") except ImportError as e: LOG.error(f"ImportError Loading import qweechat {e} {sys.path}") @@ -722,7 +723,7 @@ class MainWindow(QtWidgets.QMainWindow): self.network.disconnect_weechat() if self.network.debug_dialog: self.network.debug_dialog.close() - qweechat.config.write(self.config) + write(self.config) except Exception as e: LOG.exception(f"ERROR WeechatConsole {e}") MainWindow = None