This commit is contained in:
emdee@spm.plastiras.org 2024-02-06 18:07:32 +00:00
parent f62e28f5b4
commit ea454e27a1
5 changed files with 93 additions and 60 deletions

View file

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

15
ToDo.md
View file

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

View file

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

View file

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

View file

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