diff --git a/README.md b/README.md index 22fc9d4..067c108 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ Toxygen is powerful cross-platform [Tox](https://tox.chat/) client written in pu ### [Install](/docs/install.md) - [Contribute](/docs/contributing.md) - [Plugins](/docs/plugins.md) - [Compile](/docs/compile.md) - [Contact](/docs/contact.md) -### Supported OS: Linux and Windows +### Supported OS: Linux and Windows (only Linux is tested at the moment) ### Features: @@ -26,7 +26,7 @@ Toxygen is powerful cross-platform [Tox](https://tox.chat/) client written in pu - Faux offline file transfers - Inline images - Message splitting -- Proxy support +- Proxy support - runs over tor - Avatars - Multiprofile - Multilingual @@ -44,17 +44,20 @@ Toxygen is powerful cross-platform [Tox](https://tox.chat/) client written in pu ![Ubuntu](/docs/ubuntu.png) ![Windows](/docs/windows.png) +Windows was working but is not currently being tested. AV was working +but is not currently being tested: we're unsure of handling the AV devices +from the commandline. We need to get a working echobot that supports SOCKS5; +we were working on one in https://git.plastiras.org/emdee/toxygen_wrapper + ## Forked This hard-forked from the dead https://github.com/toxygen-project/toxygen ```next_gen``` branch. -https://git.plastiras.org/emdee/toxygen_wrapper needs packaging -is making a dependency. Just download it and copy the two directories -```wrapper``` and ```wrapper_tests``` into ```toxygen/toxygen```. - See ToDo.md to the current ToDo list. +## Wechat + You can have a [weechat](https://github.com/weechat/qweechat) console so that you can have IRC and jabber in a window as well as Tox. There's a copy of qweechat in ```thirdparty/qweechat``` backported to @@ -79,13 +82,36 @@ Weechat has a Jabber plugin to enable XMPP: ``` so you can have Tox, IRC and XMPP in the same application! -Work on Tox on this project is suspended until the -[MultiDevice](https://git.plastiras.org/emdee/tox_profile/wiki/MultiDevice-Announcements-POC) problem is solved. Fork me! +## Install -This will probably be ported to Qt6 using qtpy -https://github.com/spyder-ide/qtpy . +To install read the requirements.txt and look at the comments; there +may be things that need installing by hand or decisions to be made +on supported alternatives. + +https://git.plastiras.org/emdee/toxygen_wrapper needs packaging +on pypi as it is a dependency. Just download and install it from +https://git.plastiras.org/emdee/toxygen_wrapper + +This is being ported to Qt6 using qtpy https://github.com/spyder-ide/qtpy +It should run on PyQt5, PyQt6 and may run on PySide2 and PySide6 - YMMV. +You should be able to choose between them by setting the environment variable +QT_API to one of: pyqt5 pyqt6 pyside2 pyside6. + +To install it, look in the Makefile for the install target and type +``` +make install +``` +You should set the PIP_EXE_MSYS and PYTHON_EXE_MSYS variables and it does +``` + ${PIP_EXE_MSYS} --python ${PYTHON_EXE_MSYS} install \ + --target ${PREFIX}/lib/python${PYTHON_MINOR}/site-packages/ \ + --upgrade . +``` +and installs into PREFIX which is usually /usr/local Up-to-date code is on https://git.plastiras.org/emdee/toxygen +## MultiDevice + Work on this project is suspended until the [MultiDevice](https://git.plastiras.org/emdee/tox_profile/wiki/MultiDevice-Announcements-POC) problem is solved. Fork me! diff --git a/ToDo.md b/ToDo.md index d373750..c0e7a72 100644 --- a/ToDo.md +++ b/ToDo.md @@ -4,14 +4,14 @@ 1. There is an agravating bug where new messages are not put in the current window, and a messages waiting indicator appears. You have - to focus out of the window and then back in the window. - + to focus out of the window and then back in the window. this may be + fixed already +2. The tray icon is flaky and has been disabled - look in app.py + for bSHOW_TRAY ## Fix history -The code is in there but it's not working. - ## Fix Audio The code is in there but it's not working. It looks like audio input @@ -25,7 +25,7 @@ The code is in there but it's not working. I may have broken it trying to wire up the ability to set the video device from the command line. -## Groups +## NGC Groups 1. peer_id There has been a change of API on a field named ```group.peer_id``` The code is broken in places because I have not @@ -50,3 +50,8 @@ line. 2. https://git.plastiras.org/emdee/toxygen_wrapper needs packaging and making a dependency. + +## Standards + +There's a standard for Tox clients that this has not been tested against: +https://tox.gitbooks.io/tox-client-standard/content/general_requirements/general_requirements.html diff --git a/setup.cfg b/setup.cfg index 4d59182..d7ffc22 100644 --- a/setup.cfg +++ b/setup.cfg @@ -17,9 +17,9 @@ classifiers = [options] zip_safe = false -python_requires = ~=3.6 +python_requires = ~=3.7 include_package_data = - "*" = ["*.ui", "*.txt"] + "*" = ["*.ui", "*.txt", "*.png", "*.ico", "*.gif", "*.wav"] [options.entry_points] console_scripts = diff --git a/toxygen/__main__.py b/toxygen/__main__.py index 1675c52..3b4f3ce 100644 --- a/toxygen/__main__.py +++ b/toxygen/__main__.py @@ -37,17 +37,17 @@ __version__ = '1.0.0' # was 0.5.0+ sleep = time.sleep -os.environ['QT_API'] = 'pyqt5' +os.environ['QT_API'] = os.environ.get('QT_API', 'pyqt5') -def reset(): +def reset() -> None: Settings.reset_auto_profile() -def clean(): +def clean() -> None: """Removes libs folder""" directory = util.get_libs_directory() util.remove(directory) -def print_toxygen_version(): +def print_toxygen_version() -> None: print('toxygen ' + __version__) def setup_default_audio(): @@ -291,7 +291,7 @@ lKEEP_SETTINGS = ['uri', class A(): pass -def main(lArgs=None): +def main(lArgs=None) -> int: global oPYA from argparse import Namespace if lArgs is None: diff --git a/toxygen/app.py b/toxygen/app.py index c672697..55a007c 100644 --- a/toxygen/app.py +++ b/toxygen/app.py @@ -87,7 +87,7 @@ IDLE_PERIOD = 0.10 iNODES=8 bSHOW_TRAY=False -def setup_logging(oArgs): +def setup_logging(oArgs) -> None: global LOG logging._defaultFormatter = logging.Formatter(datefmt='%m-%d %H:%M:%S', fmt='%(levelname)s:%(name)s %(message)s') @@ -185,14 +185,14 @@ class App: # Public methods - def set_trace(self): + def set_trace(self) -> None: """unused""" LOG.debug('pdb.set_trace ') sys.stdin = sys.__stdin__ sys.stdout = sys.__stdout__ import pdb; pdb.set_trace() - def ten(self, i=0): + def ten(self, i=0) -> None: """unused""" global iI iI += 1 @@ -204,7 +204,7 @@ class App: #sys.stderr.write(f"ten '+str(iI)+' {i}"+' '+repr(LOG) +'\n') #LOG.debug('ten '+str(iI)) - def iMain(self): + def iMain(self) -> int: """ Main function of app. loads login screen if needed and starts main screen """ @@ -257,7 +257,7 @@ class App: # App executing - def _execute_app(self): + def _execute_app(self) -> None: LOG.debug("_execute_app") while True: @@ -268,7 +268,7 @@ class App: else: break - def quit(self, retval=0): + def quit(self, retval=0) -> None: LOG.debug("quit") self._stop_app() @@ -293,7 +293,7 @@ class App: raise SystemExit(retval) - def _stop_app(self): + def _stop_app(self) -> None: LOG.debug("_stop_app") self._save_profile() #? self._history.save_history() @@ -321,7 +321,7 @@ class App: # App loading - def _load_base_style(self): + def _load_base_style(self) -> None: if self._args.theme in ['', 'default']: return if qdarkstyle: @@ -341,7 +341,7 @@ class App: style += '\n' +sSTYLE self._app.setStyleSheet(style) - def _load_app_styles(self): + def _load_app_styles(self) -> None: LOG.debug(f"_load_app_styles {list(settings.built_in_themes().keys())!r}") # application color scheme if self._settings['theme'] in ['', 'default']: return @@ -371,7 +371,7 @@ class App: LOG.info('_load_app_styles: loaded theme ' +self._args.theme) break - def _load_login_screen_translations(self): + def _load_login_screen_translations(self) -> None: LOG.debug("_load_login_screen_translations") current_language, supported_languages = self._get_languages() if current_language not in supported_languages: @@ -382,13 +382,13 @@ class App: self._app.installTranslator(translator) self._app.translator = translator - def _load_icon(self): + def _load_icon(self) -> None: LOG.debug("_load_icon") icon_file = os.path.join(util.get_images_directory(), 'icon.png') self._app.setWindowIcon(QtGui.QIcon(icon_file)) @staticmethod - def _get_languages(): + def _get_languages() -> tuple: LOG.debug("_get_languages") current_locale = QtCore.QLocale() curr_language = current_locale.languageToString(current_locale.language()) @@ -396,7 +396,7 @@ class App: return curr_language, supported_languages - def _load_app_translations(self): + def _load_app_translations(self) -> None: LOG.debug("_load_app_translations") lang = settings.supported_languages()[self._settings['language']] translator = QtCore.QTranslator() @@ -404,7 +404,7 @@ class App: self._app.installTranslator(translator) self._app.translator = translator - def _select_and_load_profile(self): + def _select_and_load_profile(self) -> bool: LOG.debug("_select_and_load_profile: " +repr(self._path)) if self._path is not None: @@ -468,7 +468,7 @@ class App: # Threads - def _start_threads(self, initial_start=True): + def _start_threads(self, initial_start=True) -> None: LOG.debug(f"_start_threads before: {threading.enumerate()!r}") # init thread self._init = threads.InitThread(self._tox, @@ -491,7 +491,7 @@ class App: threads.start_file_transfer_thread() LOG.debug(f"_start_threads after: {[t.name for t in threading.enumerate()]!r}") - def _stop_threads(self, is_app_closing=True): + def _stop_threads(self, is_app_closing=True) -> None: LOG.debug("_stop_threads") self._init.stop_thread(1.0) @@ -501,7 +501,7 @@ class App: if is_app_closing: threads.stop_file_transfer_thread() - def iterate(self, n=100): + def iterate(self, n=100) -> None: interval = self._tox.iteration_interval() for i in range(n): self._tox.iterate() @@ -520,7 +520,7 @@ class App: self._app.exec_() return ls.result - def _load_existing_profile(self, profile_path): + def _load_existing_profile(self, profile_path) -> None: LOG.info("_load_existing_profile " +repr(profile_path)) assert os.path.exists(profile_path), profile_path self._profile_manager = ProfileManager(self._toxes, profile_path) @@ -536,7 +536,7 @@ class App: self._tox = self._create_tox(data, self._settings) LOG.debug("created _tox") - def _create_new_profile(self, profile_name): + def _create_new_profile(self, profile_name) -> bool: LOG.info("_create_new_profile " + profile_name) result = self._get_create_profile_screen_result() if result is None: @@ -587,7 +587,7 @@ class App: return cps.result - def _save_profile(self, data=None): + def _save_profile(self, data=None) -> None: LOG.debug("_save_profile") data = data or self._tox.get_savedata() self._profile_manager.save_profile(data) @@ -608,7 +608,7 @@ class App: self._force_exit(0) return None - def _reset(self): + def _reset(self) -> None: LOG.debug("_reset") """ Create new tox instance (new network settings) @@ -648,7 +648,7 @@ class App: text = util_ui.tr('Error:') + str(e) util_ui.message_box(text, title) - def _create_dependencies(self): + def _create_dependencies(self) -> None: LOG.info(f"_create_dependencies toxygen version {self._version}") if hasattr(self._args, 'update') and self._args.update: self._backup_service = BackupService(self._settings, @@ -794,11 +794,11 @@ class App: self._tox = retval return retval - def _force_exit(self, retval=0): + def _force_exit(self, retval=0) -> None: LOG.debug("_force_exit") sys.exit(0) - def _init_callbacks(self, ms=None): + def _init_callbacks(self, ms=None) -> None: LOG.debug("_init_callbacks") # this will block if you are not connected callbacks.init_callbacks(self._tox, self._profile, self._settings, @@ -809,21 +809,21 @@ class App: self._messenger, self._groups_service, self._contacts_provider, self._ms) - def _init_profile(self): + def _init_profile(self) -> None: LOG.debug("_init_profile") if not self._profile.has_avatar(): self._profile.reset_avatar(self._settings['identicons']) - def _kill_toxav(self): + def _kill_toxav(self) -> None: # LOG_debug("_kill_toxav") self._calls_manager.set_toxav(None) self._tox.AV.kill() - def _kill_tox(self): + def _kill_tox(self) -> None: # LOG.debug("_kill_tox") self._tox.kill() - def loop(self, n): + def loop(self, n) -> None: """ Im guessings - there are 3 sleeps - time, tox, and Qt """ @@ -834,11 +834,11 @@ class App: # NO QtCore.QCoreApplication.processEvents() sleep(interval / 1000.0) - def _test_tox(self): + def _test_tox(self) -> None: self.test_net(iMax=8) self._ms.log_console() - def test_net(self, lElts=None, oThread=None, iMax=4): + def test_net(self, lElts=None, oThread=None, iMax=4) -> None: # bootstrap LOG.debug('test_net: Calling generate_nodes: udp') @@ -906,7 +906,7 @@ class App: LOG.trace(f"Connected status #{i}: {status!r}") self.loop(2) - def _test_env(self): + def _test_env(self) -> None: _settings = self._settings if 'proxy_type' not in _settings or _settings['proxy_type'] == 0 or \ not _settings['proxy_host'] or not _settings['proxy_port']: @@ -929,7 +929,7 @@ class App: # LOG.debug(f"test_env {len(lElts)}") return env - def _test_bootstrap(self, lElts=None): + def _test_bootstrap(self, lElts=None) -> None: if lElts is None: lElts = self._settings['current_nodes_udp'] LOG.debug(f"_test_bootstrap #Elts={len(lElts)}") @@ -939,14 +939,14 @@ class App: ts.bootstrap_udp(lElts[:iNODES], [self._tox]) LOG.info("Connected status: " +repr(self._tox.self_get_connection_status())) - def _test_relays(self, lElts=None): + def _test_relays(self, lElts=None) -> None: if lElts is None: lElts = self._settings['current_nodes_tcp'] shuffle(lElts) LOG.debug(f"_test_relays {len(lElts)}") ts.bootstrap_tcp(lElts[:iNODES], [self._tox]) - def _test_nmap(self, lElts=None): + def _test_nmap(self, lElts=None) -> None: LOG.debug("_test_nmap") if not self._tox: return title = 'Extended Test Suite' @@ -980,7 +980,7 @@ class App: # LOG.info("Connected status: " +repr(self._tox.self_get_connection_status())) self._ms.log_console() - def _test_main(self): + def _test_main(self) -> None: from toxygen_tox_wrapper.tox_wrapper.tests.tests_wrapper import main as tests_main LOG.debug("_test_main") if not self._tox: return @@ -1017,11 +1017,13 @@ class GEventProcessing: self._timer = QTimer() self._timer.timeout.connect(self.process_events) self._timer.start(0) - def __enter__(self): + def __enter__(self) -> None: pass - def __exit__(self, *exc_info): + + def __exit__(self, *exc_info) -> None: self._timer.stop() - def process_events(self, idle_period=None): + + def process_events(self, idle_period=None) -> None: if idle_period is None: idle_period = self._idle_period # Cooperative yield, allow gevent to monitor file handles via libevent