Compare commits

...

3 Commits

Author SHA1 Message Date
emdee ba013b6a81 bugfixes 2023-12-10 04:43:53 +00:00
emdee 4109c822b3 rm toxygen/wrapper*/* 2023-12-10 02:42:43 +00:00
emdee 4e77ddc2de simple updates 2023-12-10 02:39:58 +00:00
101 changed files with 12215 additions and 5208 deletions

3
.gitignore vendored
View File

@ -25,4 +25,5 @@ Toxygen.egg-info
*.tox
.cache
*.db
*~
Makefile

0
.rsync Normal file
View File

130
_Bugs/segv.err Normal file
View File

@ -0,0 +1,130 @@
0
TRAC> network.c#1748:net_connect connecting socket 58 to 127.0.0.1:9050
TRAC> Messenger.c#2709:do_messenger Friend num in DHT 2 != friend num in msger 14
TRAC> Messenger.c#2723:do_messenger F[--: 0] D3385007C28852C5398393E3338E6AABE5F86EF249BF724E7404233207D4D927
TRAC> Messenger.c#2723:do_messenger F[--: 1] 98984E104B8A97CC43AF03A27BE159AC1F4CF35FADCC03D6CD5F8D67B5942A56
TRAC> TCP_common.c#203:read_TCP_packet recv buffer has 0 bytes, but requested 10 bytes
TRAC> network.c#789:loglogdata [05 = <unknown> ] =>T 2= 185.87.49.189:3389 (0: OK) | 0000000000000000...00
TRAC> network.c#789:loglogdata [05 = <unknown> ] T=> 10= 185.87.49.189:3389 (0: OK) | 010001b95731bd0d...3d
TRAC> TCP_common.c#203:read_TCP_packet recv buffer has 0 bytes, but requested 10 bytes
TRAC> network.c#789:loglogdata [05 = <unknown> ] =>T 2= 37.221.66.161:443 (0: OK) | 0000000000000000...00
TRAC> network.c#789:loglogdata [05 = <unknown> ] T=> 10= 37.221.66.161:443 (0: OK) | 01000125dd42a101...bb
TRAC> TCP_common.c#203:read_TCP_packet recv buffer has 0 bytes, but requested 10 bytes
TRAC> network.c#789:loglogdata [05 = <unknown> ] T=> 3= 172.93.52.70:33445 (0: OK) | 0000000000000000...00
TRAC> TCP_common.c#203:read_TCP_packet recv buffer has 0 bytes, but requested 2 bytes
TRAC> network.c#789:loglogdata [05 = <unknown> ] T=> 3= 139.162.110.188:33445 (0: OK) | 0000000000000000...00
TRAC> TCP_common.c#203:read_TCP_packet recv buffer has 0 bytes, but requested 2 bytes
TRAC> network.c#789:loglogdata [05 = <unknown> ] T=> 3= 37.59.63.150:33445 (0: OK) | 0000000000000000...00
TRAC> TCP_common.c#203:read_TCP_packet recv buffer has 0 bytes, but requested 2 bytes
TRAC> network.c#789:loglogdata [05 = <unknown> ] T=> 3= 130.133.110.14:33445 (0: OK) | 0000000000000000...00
TRAC> TCP_common.c#203:read_TCP_packet recv buffer has 0 bytes, but requested 2 bytes
TRAC> network.c#789:loglogdata [05 = <unknown> ] T=> 3= 37.97.185.116:33445 (0: OK) | 0000000000000000...00
TRAC> TCP_common.c#203:read_TCP_packet recv buffer has 0 bytes, but requested 2 bytes
TRAC> network.c#789:loglogdata [05 = <unknown> ] T=> 3= 85.143.221.42:33445 (0: OK) | 0000000000000000...00
TRAC> TCP_common.c#203:read_TCP_packet recv buffer has 0 bytes, but requested 2 bytes
TRAC> network.c#789:loglogdata [05 = <unknown> ] T=> 3= 104.244.74.69:38445 (0: OK) | 0000000000000000...00
TRAC> TCP_common.c#203:read_TCP_packet recv buffer has 0 bytes, but requested 2 bytes
TRAC> network.c#789:loglogdata [05 = <unknown> ] T=> 3= 49.12.229.145:3389 (0: OK) | 0000000000000000...00
TRAC> TCP_common.c#203:read_TCP_packet recv buffer has 0 bytes, but requested 2 bytes
TRAC> network.c#789:loglogdata [05 = <unknown> ] T=> 3= 168.119.209.10:33445 (0: OK) | 0000000000000000...00
TRAC> TCP_common.c#203:read_TCP_packet recv buffer has 0 bytes, but requested 2 bytes
TRAC> network.c#789:loglogdata [05 = <unknown> ] T=> 3= 81.169.136.229:33445 (0: OK) | 0000000000000000...00
TRAC> TCP_common.c#203:read_TCP_packet recv buffer has 0 bytes, but requested 2 bytes
TRAC> network.c#789:loglogdata [05 = <unknown> ] T=> 3= 91.219.59.156:33445 (0: OK) | 0000000000000000...00
TRAC> TCP_common.c#203:read_TCP_packet recv buffer has 0 bytes, but requested 2 bytes
TRAC> network.c#789:loglogdata [05 = <unknown> ] T=> 3= 46.101.197.175:3389 (0: OK) | 0000000000000000...00
TRAC> TCP_common.c#203:read_TCP_packet recv buffer has 0 bytes, but requested 2 bytes
TRAC> network.c#789:loglogdata [05 = <unknown> ] T=> 3= 198.199.98.108:33445 (0: OK) | 0000000000000000...00
TRAC> TCP_common.c#203:read_TCP_packet recv buffer has 0 bytes, but requested 2 bytes
TRAC> network.c#789:loglogdata [05 = <unknown> ] T=> 3= 130.133.110.14:33445 (0: OK) | 0000000000000000...00
TRAC> TCP_common.c#203:read_TCP_packet recv buffer has 0 bytes, but requested 2 bytes
TRAC> network.c#789:loglogdata [05 = <unknown> ] T=> 3= 49.12.229.145:3389 (0: OK) | 0000000000000000...00
TRAC> TCP_common.c#203:read_TCP_packet recv buffer has 0 bytes, but requested 2 bytes
TRAC> network.c#789:loglogdata [05 = <unknown> ] T=> 3= 188.225.9.167:33445 (0: OK) | 0000000000000000...00
TRAC> TCP_common.c#203:read_TCP_packet recv buffer has 0 bytes, but requested 2 bytes
TRAC> network.c#789:loglogdata [05 = <unknown> ] T=> 3= 5.19.249.240:38296 (0: OK) | 0000000000000000...00
TRAC> TCP_common.c#203:read_TCP_packet recv buffer has 0 bytes, but requested 2 bytes
TRAC> network.c#789:loglogdata [05 = <unknown> ] T=> 3= 94.156.35.247:3389 (0: OK) | 0000000000000000...00
TRAC> TCP_common.c#203:read_TCP_packet recv buffer has 0 bytes, but requested 2 bytes
TRAC> TCP_common.c#203:read_TCP_packet recv buffer has 0 bytes, but requested 10 bytes
TRAC> TCP_common.c#203:read_TCP_packet recv buffer has 0 bytes, but requested 10 bytes
TRAC> TCP_common.c#203:read_TCP_packet recv buffer has 0 bytes, but requested 10 bytes
TRAC> network.c#789:loglogdata [05 = <unknown> ] =>T 2= 172.93.52.70:33445 (0: OK) | 0000000000000000...00
TRAC> network.c#789:loglogdata [05 = <unknown> ] T=> 10= 172.93.52.70:33445 (0: OK) | 010001ac5d344682...a5
TRAC> TCP_common.c#203:read_TCP_packet recv buffer has 0 bytes, but requested 10 bytes
TRAC> network.c#789:loglogdata [05 = <unknown> ] =>T 2= 139.162.110.188:33445 (0: OK) | 0000000000000000...00
TRAC> network.c#789:loglogdata [05 = <unknown> ] T=> 10= 139.162.110.188:33445 (0: OK) | 0100018ba26ebc82...a5
TRAC> TCP_common.c#203:read_TCP_packet recv buffer has 0 bytes, but requested 10 bytes
TRAC> network.c#789:loglogdata [05 = <unknown> ] =>T 2= 37.59.63.150:33445 (0: OK) | 0000000000000000...00
TRAC> network.c#789:loglogdata [05 = <unknown> ] T=> 10= 37.59.63.150:33445 (0: OK) | 010001253b3f9682...a5
TRAC> TCP_common.c#203:read_TCP_packet recv buffer has 0 bytes, but requested 10 bytes
TRAC> network.c#789:loglogdata [05 = <unknown> ] =>T 2= 130.133.110.14:33445 (0: OK) | 0000000000000000...00
TRAC> network.c#789:loglogdata [05 = <unknown> ] T=> 10= 130.133.110.14:33445 (0: OK) | 01000182856e0e82...a5
TRAC> TCP_common.c#203:read_TCP_packet recv buffer has 0 bytes, but requested 10 bytes
TRAC> network.c#789:loglogdata [05 = <unknown> ] =>T 2= 37.97.185.116:33445 (0: OK) | 0000000000000000...00
TRAC> network.c#789:loglogdata [05 = <unknown> ] T=> 10= 37.97.185.116:33445 (0: OK) | 0100012561b97482...a5
TRAC> TCP_common.c#203:read_TCP_packet recv buffer has 0 bytes, but requested 10 bytes
TRAC> network.c#789:loglogdata [05 = <unknown> ] =>T 2= 85.143.221.42:33445 (0: OK) | 0000000000000000...00
TRAC> network.c#789:loglogdata [05 = <unknown> ] T=> 10= 85.143.221.42:33445 (0: OK) | 010001558fdd2a82...a5
TRAC> TCP_common.c#203:read_TCP_packet recv buffer has 0 bytes, but requested 10 bytes
TRAC> network.c#789:loglogdata [05 = <unknown> ] =>T 2= 104.244.74.69:38445 (0: OK) | 0000000000000000...00
TRAC> network.c#789:loglogdata [05 = <unknown> ] T=> 10= 104.244.74.69:38445 (0: OK) | 01000168f44a4596...2d
TRAC> TCP_common.c#203:read_TCP_packet recv buffer has 0 bytes, but requested 10 bytes
TRAC> network.c#789:loglogdata [05 = <unknown> ] =>T 2= 49.12.229.145:3389 (0: OK) | 0000000000000000...00
TRAC> network.c#789:loglogdata [05 = <unknown> ] T=> 10= 49.12.229.145:3389 (0: OK) | 010001310ce5910d...3d
TRAC> TCP_common.c#203:read_TCP_packet recv buffer has 0 bytes, but requested 10 bytes
TRAC> network.c#789:loglogdata [05 = <unknown> ] =>T 2= 168.119.209.10:33445 (0: OK) | 0000000000000000...00
TRAC> network.c#789:loglogdata [05 = <unknown> ] T=> 10= 168.119.209.10:33445 (0: OK) | 010001a877d10a82...a5
TRAC> TCP_common.c#203:read_TCP_packet recv buffer has 0 bytes, but requested 10 bytes
TRAC> network.c#789:loglogdata [05 = <unknown> ] =>T 2= 81.169.136.229:33445 (0: OK) | 0000000000000000...00
TRAC> network.c#789:loglogdata [05 = <unknown> ] T=> 10= 81.169.136.229:33445 (0: OK) | 01000151a988e582...a5
TRAC> TCP_common.c#203:read_TCP_packet recv buffer has 0 bytes, but requested 10 bytes
TRAC> network.c#789:loglogdata [05 = <unknown> ] =>T 2= 91.219.59.156:33445 (0: OK) | 0000000000000000...00
TRAC> network.c#789:loglogdata [05 = <unknown> ] T=> 10= 91.219.59.156:33445 (0: OK) | 0100015bdb3b9c82...a5
TRAC> TCP_common.c#203:read_TCP_packet recv buffer has 0 bytes, but requested 10 bytes
TRAC> network.c#789:loglogdata [05 = <unknown> ] =>T 2= 46.101.197.175:3389 (0: OK) | 0000000000000000...00
TRAC> network.c#789:loglogdata [05 = <unknown> ] T=> 10= 46.101.197.175:3389 (0: OK) | 0100012e65c5af0d...3d
TRAC> TCP_common.c#203:read_TCP_packet recv buffer has 0 bytes, but requested 10 bytes
TRAC> network.c#789:loglogdata [05 = <unknown> ] =>T 2= 198.199.98.108:33445 (0: OK) | 0000000000000000...00
TRAC> network.c#789:loglogdata [05 = <unknown> ] T=> 10= 198.199.98.108:33445 (0: OK) | 010001c6c7626c82...a5
TRAC> TCP_common.c#203:read_TCP_packet recv buffer has 0 bytes, but requested 10 bytes
TRAC> network.c#789:loglogdata [05 = <unknown> ] =>T 2= 130.133.110.14:33445 (0: OK) | 0000000000000000...00
TRAC> network.c#789:loglogdata [05 = <unknown> ] T=> 10= 130.133.110.14:33445 (0: OK) | 01000182856e0e82...a5
TRAC> TCP_common.c#203:read_TCP_packet recv buffer has 0 bytes, but requested 10 bytes
TRAC> network.c#789:loglogdata [05 = <unknown> ] =>T 2= 49.12.229.145:3389 (0: OK) | 0000000000000000...00
TRAC> network.c#789:loglogdata [05 = <unknown> ] T=> 10= 49.12.229.145:3389 (0: OK) | 010001310ce5910d...3d
TRAC> TCP_common.c#203:read_TCP_packet recv buffer has 0 bytes, but requested 10 bytes
TRAC> network.c#789:loglogdata [05 = <unknown> ] =>T 2= 188.225.9.167:33445 (0: OK) | 0000000000000000...00
TRAC> network.c#789:loglogdata [05 = <unknown> ] T=> 10= 188.225.9.167:33445 (0: OK) | 010001bce109a782...a5
TRAC> TCP_common.c#203:read_TCP_packet recv buffer has 0 bytes, but requested 10 bytes
TRAC> network.c#789:loglogdata [05 = <unknown> ] =>T 2= 5.19.249.240:38296 (0: OK) | 0000000000000000...00
TRAC> network.c#789:loglogdata [05 = <unknown> ] T=> 10= 5.19.249.240:38296 (0: OK) | 0100010513f9f095...98
TRAC> TCP_common.c#203:read_TCP_packet recv buffer has 0 bytes, but requested 10 bytes
TRAC> network.c#789:loglogdata [05 = <unknown> ] =>T 2= 94.156.35.247:3389 (0: OK) | 0000000000000000...00
TRAC> network.c#789:loglogdata [05 = <unknown> ] T=> 10= 94.156.35.247:3389 (0: OK) | 0100015e9c23f70d...3d
TRAC> TCP_common.c#203:read_TCP_packet recv buffer has 0 bytes, but requested 10 bytes
TRAC> TCP_common.c#203:read_TCP_packet recv buffer has 0 bytes, but requested 10 bytes
TRAC> TCP_common.c#203:read_TCP_packet recv buffer has 0 bytes, but requested 10 bytes
TRAC> TCP_common.c#203:read_TCP_packet recv buffer has 0 bytes, but requested 10 bytes
TRAC> TCP_common.c#203:read_TCP_packet recv buffer has 0 bytes, but requested 10 bytes
TRAC> TCP_common.c#203:read_TCP_packet recv buffer has 0 bytes, but requested 10 bytes
TRAC> TCP_common.c#203:read_TCP_packet recv buffer has 0 bytes, but requested 10 bytes
TRAC> TCP_common.c#203:read_TCP_packet recv buffer has 0 bytes, but requested 10 bytes
TRAC> TCP_common.c#203:read_TCP_packet recv buffer has 0 bytes, but requested 10 bytes
TRAC> TCP_common.c#203:read_TCP_packet recv buffer has 0 bytes, but requested 10 bytes
TRAC> TCP_common.c#203:read_TCP_packet recv buffer has 0 bytes, but requested 10 bytes
TRAC> TCP_common.c#203:read_TCP_packet recv buffer has 0 bytes, but requested 10 bytes
TRAC> TCP_common.c#203:read_TCP_packet recv buffer has 0 bytes, but requested 10 bytes
TRAC> TCP_common.c#203:read_TCP_packet recv buffer has 0 bytes, but requested 10 bytes
TRAC> TCP_common.c#203:read_TCP_packet recv buffer has 0 bytes, but requested 10 bytes
TRAC> TCP_common.c#203:read_TCP_packet recv buffer has 0 bytes, but requested 10 bytes
TRAC> TCP_common.c#203:read_TCP_packet recv buffer has 0 bytes, but requested 10 bytes
TRAC> TCP_common.c#203:read_TCP_packet recv buffer has 0 bytes, but requested 10 bytes
TRAC> TCP_common.c#203:read_TCP_packet recv buffer has 0 bytes, but requested 10 bytes
TRAC> TCP_common.c#203:read_TCP_packet recv buffer has 0 bytes, but requested 10 bytes
TRAC> TCP_common.c#203:read_TCP_packet recv buffer has 0 bytes, but requested 10 bytes
TRAC> TCP_common.c#203:read_TCP_packet recv buffer has 0 bytes, but requested 10 bytes
app.contacts.contacts_manager INFO update_groups_numbers len(groups)={len(groups)}
Thread 76 "ToxIterateThrea" received signal SIGSEGV, Segmentation fault.
[Switching to Thread 0x7ffedcb6b640 (LWP 2950427)]

View File

@ -0,0 +1,11 @@
ping tox.abilinski.com
ping: socket: Address family not supported by protocol
PING tox.abilinski.com (172.103.226.229) 56(84) bytes of data.
64 bytes from 172.103.226.229.cable.tpia.cipherkey.com (172.103.226.229): icmp_seq=1 ttl=48 time=86.6 ms
64 bytes from 172.103.226.229.cable.tpia.cipherkey.com (172.103.226.229): icmp_seq=2 ttl=48 time=83.1 ms
64 bytes from 172.103.226.229.cable.tpia.cipherkey.com (172.103.226.229): icmp_seq=3 ttl=48 time=82.9 ms
64 bytes from 172.103.226.229.cable.tpia.cipherkey.com (172.103.226.229): icmp_seq=4 ttl=48 time=83.4 ms
64 bytes from 172.103.226.229.cable.tpia.cipherkey.com (172.103.226.229): icmp_seq=5 ttl=48 time=102 ms
64 bytes from 172.103.226.229.cable.tpia.cipherkey.com (172.103.226.229): icmp_seq=6 ttl=48 time=87.4 ms
64 bytes from 172.103.226.229.cable.tpia.cipherkey.com (172.103.226.229): icmp_seq=7 ttl=48 time=84.9 ms
^C

5
toxygen/.pylint.sh Executable file
View File

@ -0,0 +1,5 @@
#!/bin/sh
ROLE=logging
/var/local/bin/pydev_pylint.bash -E -f text *py [a-nr-z]*/*py >.pylint.err
/var/local/bin/pydev_pylint.bash *py [a-nr-z]*/*py >.pylint.out

View File

@ -179,9 +179,7 @@ class App:
self._uri = uri[4:]
self._history = None
# -----------------------------------------------------------------------------------------------------------------
# Public methods
# -----------------------------------------------------------------------------------------------------------------
def set_trace(self):
"""unused"""
@ -255,9 +253,7 @@ class App:
return retval
# -----------------------------------------------------------------------------------------------------------------
# App executing
# -----------------------------------------------------------------------------------------------------------------
def _execute_app(self):
LOG.debug("_execute_app")
@ -321,9 +317,7 @@ class App:
oArgs.log_oFd.close()
delattr(oArgs, 'log_oFd')
# -----------------------------------------------------------------------------------------------------------------
# App loading
# -----------------------------------------------------------------------------------------------------------------
def _load_base_style(self):
if self._args.theme in ['', 'default']: return
@ -470,9 +464,7 @@ class App:
return True
# -----------------------------------------------------------------------------------------------------------------
# Threads
# -----------------------------------------------------------------------------------------------------------------
def _start_threads(self, initial_start=True):
LOG.debug(f"_start_threads before: {threading.enumerate()!r}")
@ -513,9 +505,7 @@ class App:
self._tox.iterate()
gevent.sleep(interval / 1000.0)
# -----------------------------------------------------------------------------------------------------------------
# Profiles
# -----------------------------------------------------------------------------------------------------------------
def _select_profile(self):
LOG.debug("_select_profile")
@ -600,9 +590,7 @@ class App:
data = data or self._tox.get_savedata()
self._profile_manager.save_profile(data)
# -----------------------------------------------------------------------------------------------------------------
# Other private methods
# -----------------------------------------------------------------------------------------------------------------
def _enter_password(self, data):
"""
@ -967,17 +955,18 @@ class App:
reply = util_ui.question(text, title)
if not reply: return
if self._args.proxy_type == 0:
sProt = "udp4"
else:
sProt = "tcp4"
if lElts is None:
if self._args.proxy_type == 0:
sProt = "udp4"
lElts = self._settings['current_nodes_tcp']
lElts = self._settings['current_nodes_udp']
else:
sProt = "tcp4"
lElts = self._settings['current_nodes_tcp']
shuffle(lElts)
try:
ts.bootstrap_iNmapInfo(lElts, self._args, sProt)
self._ms.log_console()
except Exception as e:
LOG.error(f"test_nmap ' +' : {e}")
LOG.error('_test_nmap(): ' \
@ -990,7 +979,7 @@ class App:
self._ms.log_console()
def _test_main(self):
from tests.tests_socks import main as tests_main
from toxygen_wrapper.wrapper_tests.tests_wrapper import main as tests_main
LOG.debug("_test_main")
if not self._tox: return
title = 'Extended Test Suite'

View File

@ -17,9 +17,7 @@ class Call:
is_active = property(get_is_active, set_is_active)
# -----------------------------------------------------------------------------------------------------------------
# Audio
# -----------------------------------------------------------------------------------------------------------------
def get_in_audio(self):
return self._in_audio
@ -37,9 +35,7 @@ class Call:
out_audio = property(get_out_audio, set_out_audio)
# -----------------------------------------------------------------------------------------------------------------
# Video
# -----------------------------------------------------------------------------------------------------------------
def get_in_video(self):
return self._in_video

View File

@ -91,9 +91,7 @@ class AV(common.tox_save.ToxAvSave):
def __contains__(self, friend_number):
return friend_number in self._calls
# -----------------------------------------------------------------------------------------------------------------
# Calls
# -----------------------------------------------------------------------------------------------------------------
def __call__(self, friend_number, audio, video):
"""Call friend with specified number"""
@ -107,7 +105,7 @@ class AV(common.tox_save.ToxAvSave):
self._toxav.call(friend_number,
self._audio_krate_tox_audio if audio else 0,
self._audio_krate_tox_video if video else 0)
except ArgumentError as e:
except Exception as e:
LOG.warn(f"_toxav.call already has {friend_number}")
return
self._calls[friend_number] = Call(audio, video)
@ -116,7 +114,7 @@ class AV(common.tox_save.ToxAvSave):
def accept_call(self, friend_number, audio_enabled, video_enabled):
# obsolete
return call_accept_call(self, friend_number, audio_enabled, video_enabled)
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}" +
@ -189,9 +187,7 @@ class AV(common.tox_save.ToxAvSave):
def is_video_call(self, number):
return number in self and self._calls[number].in_video
# -----------------------------------------------------------------------------------------------------------------
# Threads
# -----------------------------------------------------------------------------------------------------------------
def start_audio_thread(self):
"""
@ -350,9 +346,7 @@ class AV(common.tox_save.ToxAvSave):
self._video_thread = None
self._video = None
# -----------------------------------------------------------------------------------------------------------------
# Incoming chunks
# -----------------------------------------------------------------------------------------------------------------
def audio_chunk(self, samples, channels_count, rate):
"""
@ -388,9 +382,7 @@ class AV(common.tox_save.ToxAvSave):
LOG.debug(f"audio_chunk output_device_index={iOutput} rate={rate} channels={channels_count}")
self._out_stream.write(samples)
# -----------------------------------------------------------------------------------------------------------------
# AV sending
# -----------------------------------------------------------------------------------------------------------------
def send_audio_data(self, data, count, *largs, **kwargs):
pcm = data

View File

@ -30,9 +30,7 @@ class CallsManager:
def set_toxav(self, toxav):
self._callav.set_toxav(toxav)
# -----------------------------------------------------------------------------------------------------------------
# Events
# -----------------------------------------------------------------------------------------------------------------
def get_call_started_event(self):
return self._call_started_event
@ -44,9 +42,7 @@ class CallsManager:
call_finished_event = property(get_call_finished_event)
# -----------------------------------------------------------------------------------------------------------------
# AV support
# -----------------------------------------------------------------------------------------------------------------
def call_click(self, audio=True, video=False):
"""User clicked audio button in main window"""
@ -159,9 +155,7 @@ class CallsManager:
if friend_number in self._callav:
self._callav.finish_call(friend_number, True)
# -----------------------------------------------------------------------------------------------------------------
# Private methods
# -----------------------------------------------------------------------------------------------------------------
def _get_incoming_call_widget(self, friend_number, text, friend_name):
return av_widgets.IncomingCallWidget(self._settings, self, friend_number, text, friend_name)

View File

@ -43,6 +43,6 @@ def download_nodes_list(settings, oArgs):
def _save_nodes(nodes, app):
if not nodes:
return
with open(_get_nodes_path(oArgs=app._args), 'wb') as fl:
LOG.info("Saving nodes to " +_get_nodes_path())
with open(_get_nodes_path(app._args), 'wb') as fl:
LOG.info("Saving nodes to " +_get_nodes_path(app._args))
fl.write(nodes)

View File

@ -35,9 +35,7 @@ class BaseContact:
self._avatar_changed_event = event.Event()
self.init_widget()
# -----------------------------------------------------------------------------------------------------------------
# Name - current name or alias of user
# -----------------------------------------------------------------------------------------------------------------
def get_name(self):
return self._name
@ -57,9 +55,7 @@ class BaseContact:
name_changed_event = property(get_name_changed_event)
# -----------------------------------------------------------------------------------------------------------------
# Status message
# -----------------------------------------------------------------------------------------------------------------
def get_status_message(self):
return self._status_message
@ -79,9 +75,7 @@ class BaseContact:
status_message_changed_event = property(get_status_message_changed_event)
# -----------------------------------------------------------------------------------------------------------------
# Status
# -----------------------------------------------------------------------------------------------------------------
def get_status(self):
return self._status
@ -100,31 +94,30 @@ class BaseContact:
status_changed_event = property(get_status_changed_event)
# -----------------------------------------------------------------------------------------------------------------
# TOX ID. WARNING: for friend it will return public key, for profile - full address
# -----------------------------------------------------------------------------------------------------------------
def get_tox_id(self):
return self._tox_id
tox_id = property(get_tox_id)
# -----------------------------------------------------------------------------------------------------------------
# Avatars
# -----------------------------------------------------------------------------------------------------------------
def load_avatar(self):
"""
Tries to load avatar of contact or uses default avatar
"""
avatar_path = self.get_avatar_path()
width = self._widget.avatar_label.width()
pixmap = QtGui.QPixmap(avatar_path)
self._widget.avatar_label.setPixmap(pixmap.scaled(width, width, QtCore.Qt.KeepAspectRatio,
QtCore.Qt.SmoothTransformation))
self._widget.avatar_label.repaint()
self._avatar_changed_event(avatar_path)
try:
avatar_path = self.get_avatar_path()
width = self._widget.avatar_label.width()
pixmap = QtGui.QPixmap(avatar_path)
self._widget.avatar_label.setPixmap(pixmap.scaled(width, width, QtCore.Qt.KeepAspectRatio,
QtCore.Qt.SmoothTransformation))
self._widget.avatar_label.repaint()
self._avatar_changed_event(avatar_path)
except Exception as e:
pass
def reset_avatar(self, generate_new):
avatar_path = self.get_avatar_path()
if os.path.isfile(avatar_path) and not avatar_path == self._get_default_avatar_path():
@ -165,9 +158,7 @@ class BaseContact:
avatar_changed_event = property(get_avatar_changed_event)
# -----------------------------------------------------------------------------------------------------------------
# Widgets
# -----------------------------------------------------------------------------------------------------------------
def init_widget(self):
self._widget.name.setText(self._name)
@ -177,9 +168,7 @@ class BaseContact:
self._widget.connection_status.update(self._status)
self.load_avatar()
# -----------------------------------------------------------------------------------------------------------------
# Private methods
# -----------------------------------------------------------------------------------------------------------------
@staticmethod
def _get_default_avatar_path():

View File

@ -1,10 +1,8 @@
from pydenticon import Generator
import hashlib
from pydenticon import Generator
# -----------------------------------------------------------------------------------------------------------------
# Typing notifications
# -----------------------------------------------------------------------------------------------------------------
class BaseTypingNotificationHandler:
@ -30,9 +28,7 @@ class FriendTypingNotificationHandler(BaseTypingNotificationHandler):
BaseTypingNotificationHandler.DEFAULT_HANDLER = BaseTypingNotificationHandler()
# -----------------------------------------------------------------------------------------------------------------
# Identicons support
# -----------------------------------------------------------------------------------------------------------------
def generate_avatar(public_key):

View File

@ -42,9 +42,7 @@ class Contact(basecontact.BaseContact):
if hasattr(self, '_message_getter'):
del self._message_getter
# -----------------------------------------------------------------------------------------------------------------
# History support
# -----------------------------------------------------------------------------------------------------------------
def load_corr(self, first_time=True):
"""
@ -121,9 +119,7 @@ class Contact(basecontact.BaseContact):
return TextMessage(message, author, unix_time, message_type, unique_id)
# -----------------------------------------------------------------------------------------------------------------
# Unsent messages
# -----------------------------------------------------------------------------------------------------------------
def get_unsent_messages(self):
"""
@ -136,10 +132,11 @@ class Contact(basecontact.BaseContact):
"""
:return list of unsent messages for saving
"""
message = list(filter(lambda m: m.author is not None
# and m.tox_message_id == tox_message_id,
messages = filter(lambda m: m.author is not None
and m.author.type == MESSAGE_AUTHOR['NOT_SENT']
and m.tox_message_id == tox_message_id,
self._corr))[0]
self._corr)
# was message = list(...)[0]
return list(messages)
def mark_as_sent(self, tox_message_id):
@ -151,9 +148,7 @@ class Contact(basecontact.BaseContact):
# wrapped C/C++ object of type QLabel has been deleted
LOG.error(f"Mark as sent: {ex!s}")
# -----------------------------------------------------------------------------------------------------------------
# Message deletion
# -----------------------------------------------------------------------------------------------------------------
def delete_message(self, message_id):
elem = list(filter(lambda m: m.message_id == message_id, self._corr))[0]
@ -199,9 +194,7 @@ class Contact(basecontact.BaseContact):
self._corr))
self._unsaved_messages = len(self.get_unsent_messages())
# -----------------------------------------------------------------------------------------------------------------
# Chat history search
# -----------------------------------------------------------------------------------------------------------------
def search_string(self, search_string):
self._search_string, self._search_index = search_string, 0
@ -234,9 +227,7 @@ class Contact(basecontact.BaseContact):
return i
return None # not found
# -----------------------------------------------------------------------------------------------------------------
# Current text - text from message area
# -----------------------------------------------------------------------------------------------------------------
def get_curr_text(self):
return self._curr_text
@ -246,9 +237,7 @@ class Contact(basecontact.BaseContact):
curr_text = property(get_curr_text, set_curr_text)
# -----------------------------------------------------------------------------------------------------------------
# Alias support
# -----------------------------------------------------------------------------------------------------------------
def set_name(self, value):
"""
@ -264,9 +253,7 @@ class Contact(basecontact.BaseContact):
def has_alias(self):
return self._alias
# -----------------------------------------------------------------------------------------------------------------
# Visibility in friends' list
# -----------------------------------------------------------------------------------------------------------------
def get_visibility(self):
return self._visible
@ -276,9 +263,7 @@ class Contact(basecontact.BaseContact):
visibility = property(get_visibility, set_visibility)
# -----------------------------------------------------------------------------------------------------------------
# Unread messages and other actions from friend
# -----------------------------------------------------------------------------------------------------------------
def get_actions(self):
return self._new_actions
@ -306,9 +291,7 @@ class Contact(basecontact.BaseContact):
messages = property(get_messages)
# -----------------------------------------------------------------------------------------------------------------
# Friend's or group's number (can be used in toxcore)
# -----------------------------------------------------------------------------------------------------------------
def get_number(self):
return self._number
@ -318,25 +301,19 @@ class Contact(basecontact.BaseContact):
number = property(get_number, set_number)
# -----------------------------------------------------------------------------------------------------------------
# Typing notifications
# -----------------------------------------------------------------------------------------------------------------
def get_typing_notification_handler(self):
return common.BaseTypingNotificationHandler.DEFAULT_HANDLER
typing_notification_handler = property(get_typing_notification_handler)
# -----------------------------------------------------------------------------------------------------------------
# Context menu support
# -----------------------------------------------------------------------------------------------------------------
def get_context_menu_generator(self):
return BaseContactMenuGenerator(self)
# -----------------------------------------------------------------------------------------------------------------
# Filtration support
# -----------------------------------------------------------------------------------------------------------------
def set_widget(self, widget):
self._widget = widget

View File

@ -8,9 +8,7 @@ global LOG
import logging
LOG = logging.getLogger('app')
# -----------------------------------------------------------------------------------------------------------------
# Builder
# -----------------------------------------------------------------------------------------------------------------
def _create_menu(menu_name, parent):
menu_name = menu_name or ''
@ -83,9 +81,7 @@ class ContactMenuBuilder:
self._actions[self._index] = (text, handler)
self._index += 1
# -----------------------------------------------------------------------------------------------------------------
# Generators
# -----------------------------------------------------------------------------------------------------------------
class BaseContactMenuGenerator:
@ -96,9 +92,7 @@ class BaseContactMenuGenerator:
def generate(self, plugin_loader, contacts_manager, main_screen, settings, number, groups_service, history_loader):
return ContactMenuBuilder().build()
# -----------------------------------------------------------------------------------------------------------------
# Private methods
# -----------------------------------------------------------------------------------------------------------------
def _generate_copy_menu_builder(self, main_screen):
copy_menu_builder = ContactMenuBuilder()
@ -150,9 +144,7 @@ class FriendMenuGenerator(BaseContactMenuGenerator):
return menu
# -----------------------------------------------------------------------------------------------------------------
# Private methods
# -----------------------------------------------------------------------------------------------------------------
@staticmethod
def _generate_plugins_menu_builder(plugin_loader, number):

View File

@ -28,15 +28,13 @@ class ContactProvider(tox_save.ToxSave):
self._group_peer_factory = group_peer_factory
self._cache = {} # key - contact's public key, value - contact instance
# -----------------------------------------------------------------------------------------------------------------
# Friends
# -----------------------------------------------------------------------------------------------------------------
def get_friend_by_number(self, friend_number):
try:
public_key = self._tox.friend_get_public_key(friend_number)
except Exception as e:
LOG_WARN(f"get_friend_by_number NO {friend_number} {e} ")
LOG_WARN(f"CP.get_friend_by_number NO {friend_number} {e} ")
return None
return self.get_friend_by_public_key(public_key)
@ -46,7 +44,7 @@ class ContactProvider(tox_save.ToxSave):
return friend
friend = self._friend_factory.create_friend_by_public_key(public_key)
self._add_to_cache(public_key, friend)
LOG_INFO(f"get_friend_by_public_key ADDED {friend} ")
LOG_INFO(f"CP.get_friend_by_public_key ADDED {friend} ")
return friend
@ -54,15 +52,13 @@ class ContactProvider(tox_save.ToxSave):
try:
friend_numbers = self._tox.self_get_friend_list()
except Exception as e:
LOG_WARN(f"get_all_friends NO {friend_numbers} {e} ")
LOG_WARN(f"CP.get_all_friends NO {friend_numbers} {e} ")
return None
friends = map(lambda n: self.get_friend_by_number(n), friend_numbers)
return list(friends)
# -----------------------------------------------------------------------------------------------------------------
# Groups
# -----------------------------------------------------------------------------------------------------------------
def get_all_groups(self):
"""from callbacks"""
@ -75,11 +71,11 @@ class ContactProvider(tox_save.ToxSave):
# failsafe in case there are bogus None groups?
fgroups = list(filter(lambda x: x, groups))
if len(fgroups) != len_groups:
LOG_WARN(f"are there are bogus None groups in libtoxcore? {len(fgroups)} != {len_groups}")
LOG_WARN(f"CP.are there are bogus None groups in libtoxcore? {len(fgroups)} != {len_groups}")
for group_num in group_numbers:
group = self.get_group_by_number(group_num)
if group is None:
LOG_ERROR(f"there are bogus None groups in libtoxcore {group_num}!")
LOG_ERROR(f"There are bogus None groups in libtoxcore {group_num}!")
# fixme: do something
groups = fgroups
return groups
@ -87,7 +83,7 @@ class ContactProvider(tox_save.ToxSave):
def get_group_by_number(self, group_number):
group = None
try:
LOG_INFO(f"CP.group_get_number {group_number} ")
LOG_INFO(f"CP.CP.group_get_number {group_number} ")
# original code
chat_id = self._tox.group_get_chat_id(group_number)
if chat_id is None:
@ -95,16 +91,16 @@ class ContactProvider(tox_save.ToxSave):
elif chat_id == '-1':
LOG_ERROR(f"get_group_by_number <0 chat_id ({group_number})")
else:
LOG_INFO(f"group_get_number {group_number} {chat_id}")
LOG_INFO(f"CP.group_get_number {group_number} {chat_id}")
group = self.get_group_by_chat_id(chat_id)
if group is None or group == '-1':
LOG_WARN(f"get_group_by_number leaving {group} ({group_number})")
LOG_WARN(f"CP.get_group_by_number leaving {group} ({group_number})")
#? iRet = self._tox.group_leave(group_number)
# invoke in main thread?
# self._contacts_manager.delete_group(group_number)
return group
except Exception as e:
LOG_WARN(f"group_get_number {group_number} {e}")
LOG_WARN(f"CP.group_get_number {group_number} {e}")
return None
def get_group_by_chat_id(self, chat_id):
@ -131,9 +127,7 @@ class ContactProvider(tox_save.ToxSave):
return group
# -----------------------------------------------------------------------------------------------------------------
# Group peers
# -----------------------------------------------------------------------------------------------------------------
def get_all_group_peers(self):
return list()
@ -148,16 +142,12 @@ class ContactProvider(tox_save.ToxSave):
return self._get_group_peer(group, peer)
# -----------------------------------------------------------------------------------------------------------------
# All contacts
# -----------------------------------------------------------------------------------------------------------------
def get_all(self):
return self.get_all_friends() + self.get_all_groups() + self.get_all_group_peers()
# -----------------------------------------------------------------------------------------------------------------
# Caching
# -----------------------------------------------------------------------------------------------------------------
def clear_cache(self):
self._cache.clear()
@ -166,9 +156,7 @@ class ContactProvider(tox_save.ToxSave):
if contact_public_key in self._cache:
del self._cache[contact_public_key]
# -----------------------------------------------------------------------------------------------------------------
# Private methods
# -----------------------------------------------------------------------------------------------------------------
def _get_contact_from_cache(self, public_key):
return self._cache[public_key] if public_key in self._cache else None

View File

@ -105,17 +105,13 @@ class ContactsManager(ToxSave):
return self._contacts[self._active_contact].tox_id == contact.tox_id
# -----------------------------------------------------------------------------------------------------------------
# Reconnection support
# -----------------------------------------------------------------------------------------------------------------
def reset_contacts_statuses(self):
for contact in self._contacts:
contact.status = None
# -----------------------------------------------------------------------------------------------------------------
# Work with active friend
# -----------------------------------------------------------------------------------------------------------------
def get_active(self):
return self._active_contact
@ -204,9 +200,7 @@ class ContactsManager(ToxSave):
def is_active_a_group_chat_peer(self):
return type(self.get_curr_contact()) is GroupPeerContact
# -----------------------------------------------------------------------------------------------------------------
# Filtration
# -----------------------------------------------------------------------------------------------------------------
def filtration_and_sorting(self, sorting=0, filter_str=''):
"""
@ -286,9 +280,7 @@ class ContactsManager(ToxSave):
"""
self.filtration_and_sorting(self._sorting, self._filter_string)
# -----------------------------------------------------------------------------------------------------------------
# Contact getters
# -----------------------------------------------------------------------------------------------------------------
def get_friend_by_number(self, number):
return list(filter(lambda c: c.number == number and type(c) is Friend, self._contacts))[0]
@ -324,9 +316,7 @@ class ContactsManager(ToxSave):
def is_active_online(self):
return self._active_contact + 1 and self.get_curr_contact().status is not None
# -----------------------------------------------------------------------------------------------------------------
# Work with friends (remove, block, set alias, get public key)
# -----------------------------------------------------------------------------------------------------------------
def set_alias(self, num):
"""
@ -411,9 +401,7 @@ class ContactsManager(ToxSave):
self.add_friend(tox_id)
self.save_profile()
# -----------------------------------------------------------------------------------------------------------------
# Groups support
# -----------------------------------------------------------------------------------------------------------------
def get_group_chats(self):
return list(filter(lambda c: type(c) is GroupChat, self._contacts))
@ -423,7 +411,7 @@ class ContactsManager(ToxSave):
group = self._contact_provider.get_group_by_number(group_number)
if group is None:
LOG.warn(f"CM.add_group: NULL group from group_number={group_number}")
elif group < 0:
elif type(group) == int and group < 0:
LOG.warn(f"CM.add_group: NO group from group={group} group_number={group_number}")
else:
LOG.info(f"CM.add_group: Adding group {group._name}")
@ -441,9 +429,7 @@ class ContactsManager(ToxSave):
num = self._contacts.index(group)
self._delete_contact(num)
# -----------------------------------------------------------------------------------------------------------------
# Groups private messaging
# -----------------------------------------------------------------------------------------------------------------
def add_group_peer(self, group, peer):
contact = self._contact_provider.get_group_peer_by_id(group, peer.id)
@ -485,9 +471,7 @@ class ContactsManager(ToxSave):
return suggested_names[0]
# -----------------------------------------------------------------------------------------------------------------
# Friend requests
# -----------------------------------------------------------------------------------------------------------------
def send_friend_request(self, sToxPkOrId, message):
"""
@ -551,9 +535,7 @@ class ContactsManager(ToxSave):
def can_send_typing_notification(self):
return self._settings['typing_notifications'] and not self.is_active_a_group_chat_peer()
# -----------------------------------------------------------------------------------------------------------------
# Contacts numbers update
# -----------------------------------------------------------------------------------------------------------------
def update_friends_numbers(self):
for friend in self._contact_provider.get_all_friends():
@ -581,9 +563,7 @@ class ContactsManager(ToxSave):
for group in groups:
group.remove_all_peers_except_self()
# -----------------------------------------------------------------------------------------------------------------
# Private methods
# -----------------------------------------------------------------------------------------------------------------
def _load_contacts(self):
self._load_friends()
@ -608,9 +588,7 @@ class ContactsManager(ToxSave):
def _load_groups(self):
self._contacts.extend(self._contact_provider.get_all_groups())
# -----------------------------------------------------------------------------------------------------------------
# Current contact subscriptions
# -----------------------------------------------------------------------------------------------------------------
def _subscribe_to_events(self, contact):
contact.name_changed_event.add_callback(self._current_contact_name_changed)

View File

@ -14,9 +14,7 @@ class Friend(contact.Contact):
self._receipts = 0
self._typing_notification_handler = common.FriendTypingNotificationHandler(number)
# -----------------------------------------------------------------------------------------------------------------
# File transfers support
# -----------------------------------------------------------------------------------------------------------------
def insert_inline(self, before_message_id, inline):
"""
@ -52,23 +50,17 @@ class Friend(contact.Contact):
self._corr = list(filter(lambda m: not (type(m) is UnsentFileMessage and m.message_id == message_id),
self._corr))
# -----------------------------------------------------------------------------------------------------------------
# Full status
# -----------------------------------------------------------------------------------------------------------------
def get_full_status(self):
return self._status_message
# -----------------------------------------------------------------------------------------------------------------
# Typing notifications
# -----------------------------------------------------------------------------------------------------------------
def get_typing_notification_handler(self):
return self._typing_notification_handler
# -----------------------------------------------------------------------------------------------------------------
# Context menu support
# -----------------------------------------------------------------------------------------------------------------
def get_context_menu_generator(self):
return FriendMenuGenerator(self)

View File

@ -1,5 +1,5 @@
from common.tox_save import ToxSave
from contacts.friend import Friend
from common.tox_save import ToxSave
class FriendFactory(ToxSave):
@ -32,9 +32,7 @@ class FriendFactory(ToxSave):
return friend
# -----------------------------------------------------------------------------------------------------------------
# Private methods
# -----------------------------------------------------------------------------------------------------------------
def _create_friend_item(self):
"""

View File

@ -35,9 +35,7 @@ class GroupChat(contact.Contact, ToxSave):
def get_context_menu_generator(self):
return GroupMenuGenerator(self)
# -----------------------------------------------------------------------------------------------------------------
# Properties
# -----------------------------------------------------------------------------------------------------------------
def get_is_private(self):
return self._is_private
@ -63,9 +61,7 @@ class GroupChat(contact.Contact, ToxSave):
peers_limit = property(get_peers_limit, set_peers_limit)
# -----------------------------------------------------------------------------------------------------------------
# Peers methods
# -----------------------------------------------------------------------------------------------------------------
def get_self_peer(self):
return self._peers[0]
@ -156,9 +152,7 @@ class GroupChat(contact.Contact, ToxSave):
#
bans = property(get_bans)
# -----------------------------------------------------------------------------------------------------------------
# Private methods
# -----------------------------------------------------------------------------------------------------------------
@staticmethod
def _get_default_avatar_path():

View File

@ -43,9 +43,7 @@ class GroupFactory(ToxSave):
return group
# -----------------------------------------------------------------------------------------------------------------
# Private methods
# -----------------------------------------------------------------------------------------------------------------
def _create_group_item(self):
"""
@ -55,7 +53,7 @@ class GroupFactory(ToxSave):
return self._items_factory.create_contact_item()
def _get_group_number_by_chat_id(self, chat_id):
for i in range(self._tox.group_get_number_groups()):
for i in range(self._tox.group_get_number_groups()+100):
if self._tox.group_get_chat_id(i) == chat_id:
return i
return -1

View File

@ -35,9 +35,7 @@ class Profile(basecontact.BaseContact, tox_save.ToxSave):
self._waiting_for_reconnection = False
self._timer = None
# -----------------------------------------------------------------------------------------------------------------
# Edit current user's data
# -----------------------------------------------------------------------------------------------------------------
def change_status(self):
"""
@ -72,9 +70,7 @@ class Profile(basecontact.BaseContact, tox_save.ToxSave):
self._sToxId = self._tox.self_get_address()
return self._sToxId
# -----------------------------------------------------------------------------------------------------------------
# Reset
# -----------------------------------------------------------------------------------------------------------------
def restart(self):
"""

View File

@ -1,11 +1,11 @@
from os import chdir, remove, rename
from os.path import basename, dirname, exists, getsize
from os.path import basename, getsize, exists, dirname
from time import time
from common.event import Event
from middleware.threads import invoke_in_main_thread
from wrapper.tox import Tox
from wrapper.toxcore_enums_and_consts import TOX_FILE_CONTROL, TOX_FILE_KIND
from wrapper.toxcore_enums_and_consts import TOX_FILE_KIND, TOX_FILE_CONTROL
FILE_TRANSFER_STATE = {
'RUNNING': 0,
@ -78,6 +78,9 @@ class FileTransfer:
def get_file_id(self):
return self._file_id
# WTF
def get_file_id(self):
return self._tox.file_get_file_id(self._friend_number, self._file_number)
file_id = property(get_file_id)
@ -112,9 +115,6 @@ class FileTransfer:
if self._tox.file_control(self._friend_number, self._file_number, control):
self.set_state(control)
def get_file_id(self):
return self._tox.file_get_file_id(self._friend_number, self._file_number)
def _signal(self):
percentage = self._done / self._size if self._size else 0
if self._creation_time is None or not percentage:
@ -126,9 +126,7 @@ class FileTransfer:
def _finished(self):
self._finished_event(self._friend_number, self._file_number)
# -----------------------------------------------------------------------------------------------------------------
# Send file
# -----------------------------------------------------------------------------------------------------------------
class SendTransfer(FileTransfer):
@ -223,9 +221,7 @@ class SendFromFileBuffer(SendTransfer):
chdir(dirname(self._path))
remove(self._path)
# -----------------------------------------------------------------------------------------------------------------
# Receive file
# -----------------------------------------------------------------------------------------------------------------
class ReceiveTransfer(FileTransfer):

View File

@ -33,9 +33,7 @@ class FileTransfersHandler(ToxSave):
self._settings['paused_file_transfers'] = self._paused_file_transfers if self._settings['resend_files'] else {}
self._settings.save()
# -----------------------------------------------------------------------------------------------------------------
# File transfers support
# -----------------------------------------------------------------------------------------------------------------
def incoming_file_transfer(self, friend_number, file_number, size, file_name):
"""
@ -255,9 +253,7 @@ class FileTransfersHandler(ToxSave):
self._paused_file_transfers[ft.file_id] = [ft.path, friend_num, True, ft.total_size()]
self.cancel_transfer(friend_num, file_num, True)
# -----------------------------------------------------------------------------------------------------------------
# Avatars support
# -----------------------------------------------------------------------------------------------------------------
def send_avatar(self, friend_number, avatar_path=None):
"""
@ -297,9 +293,7 @@ class FileTransfersHandler(ToxSave):
for friend in filter(self._is_friend_online, friends):
self.send_avatar(friend.number)
# -----------------------------------------------------------------------------------------------------------------
# Private methods
# -----------------------------------------------------------------------------------------------------------------
def _is_friend_online(self, friend_number):
friend = self._get_friend_by_number(friend_number)

View File

@ -75,9 +75,7 @@ class FileTransfersMessagesService:
return tm
# -----------------------------------------------------------------------------------------------------------------
# Private methods
# -----------------------------------------------------------------------------------------------------------------
def _is_friend_active(self, friend_number):
if not self._contacts_manager.is_active_a_friend():

View File

@ -15,9 +15,7 @@ class GroupChatPeer:
self._is_muted = is_muted
# unused?
self._kind = 'grouppeer'
# -----------------------------------------------------------------------------------------------------------------
# Readonly properties
# -----------------------------------------------------------------------------------------------------------------
def get_id(self):
return self._peer_id
@ -34,9 +32,7 @@ class GroupChatPeer:
is_current_user = property(get_is_current_user)
# -----------------------------------------------------------------------------------------------------------------
# Read-write properties
# -----------------------------------------------------------------------------------------------------------------
def get_name(self):
return self._name

View File

@ -31,9 +31,7 @@ class GroupsService(tox_save.ToxSave):
for group in self._get_all_groups():
group.set_tox(tox)
# -----------------------------------------------------------------------------------------------------------------
# Groups creation
# -----------------------------------------------------------------------------------------------------------------
def create_new_gc(self, name, privacy_state, nick, status):
try:
@ -74,9 +72,7 @@ class GroupsService(tox_save.ToxSave):
group.status = constants.TOX_USER_STATUS['NONE']
self._contacts_manager.update_filtration()
# -----------------------------------------------------------------------------------------------------------------
# Groups reconnect and leaving
# -----------------------------------------------------------------------------------------------------------------
def leave_group(self, group_number):
if type(group_number) == int:
@ -95,9 +91,7 @@ class GroupsService(tox_save.ToxSave):
group.status = constants.TOX_USER_STATUS['NONE']
self._clear_peers_list(group)
# -----------------------------------------------------------------------------------------------------------------
# Group invites
# -----------------------------------------------------------------------------------------------------------------
def invite_friend(self, friend_number, group_number):
if self._tox.friend_get_connection_status(friend_number) == TOX_CONNECTION['NONE']:
@ -142,9 +136,7 @@ class GroupsService(tox_save.ToxSave):
group_invites_count = property(get_group_invites_count)
# -----------------------------------------------------------------------------------------------------------------
# Group info methods
# -----------------------------------------------------------------------------------------------------------------
def update_group_info(self, group):
group.name = self._tox.group_get_name(group.number)
@ -190,9 +182,7 @@ class GroupsService(tox_save.ToxSave):
self._tox.group_founder_set_privacy_state(group.number, privacy_state)
group.is_private = is_private
# -----------------------------------------------------------------------------------------------------------------
# Peers list
# -----------------------------------------------------------------------------------------------------------------
def generate_peers_list(self):
if not self._contacts_manager.is_active_a_group():
@ -210,9 +200,7 @@ class GroupsService(tox_save.ToxSave):
self._screen = widgets_factory.create_self_peer_screen_window(group)
self._screen.show()
# -----------------------------------------------------------------------------------------------------------------
# Peers actions
# -----------------------------------------------------------------------------------------------------------------
def set_new_peer_role(self, group, peer, role):
self._tox.group_mod_set_role(group.number, peer.id, role)
@ -231,9 +219,7 @@ class GroupsService(tox_save.ToxSave):
self_peer.status = status
self.generate_peers_list()
# -----------------------------------------------------------------------------------------------------------------
# Bans support
# -----------------------------------------------------------------------------------------------------------------
def show_bans_list(self, group):
return
@ -250,9 +236,7 @@ class GroupsService(tox_save.ToxSave):
def cancel_ban(self, group_number, ban_id):
self._tox.group_mod_remove_ban(group_number, ban_id)
# -----------------------------------------------------------------------------------------------------------------
# Private methods
# -----------------------------------------------------------------------------------------------------------------
def _add_new_group_by_number(self, group_number):
LOG.debug(f"_add_new_group_by_number group_number={group_number}")

View File

@ -3,9 +3,7 @@ from wrapper.toxcore_enums_and_consts import *
from ui.widgets import *
# -----------------------------------------------------------------------------------------------------------------
# Builder
# -----------------------------------------------------------------------------------------------------------------
class PeerListBuilder:
@ -63,9 +61,7 @@ class PeerListBuilder:
self._peers[self._index] = peer
self._index += 1
# -----------------------------------------------------------------------------------------------------------------
# Generators
# -----------------------------------------------------------------------------------------------------------------
class PeersListGenerator:

View File

@ -51,9 +51,7 @@ class Database:
os.remove(path)
LOG.info('Db opened: ' +path)
# -----------------------------------------------------------------------------------------------------------------
# Public methods
# -----------------------------------------------------------------------------------------------------------------
def save(self):
if self._toxes.has_password():
@ -178,9 +176,7 @@ class Database:
return Database.MessageGetter(self._path, tox_id)
# -----------------------------------------------------------------------------------------------------------------
# Messages loading
# -----------------------------------------------------------------------------------------------------------------
class MessageGetter:
@ -225,9 +221,7 @@ class Database:
def _disconnect(self):
self._db.close()
# -----------------------------------------------------------------------------------------------------------------
# Private methods
# -----------------------------------------------------------------------------------------------------------------
def _connect(self):
return connect(self._path, timeout=TIMEOUT)

View File

@ -22,9 +22,7 @@ class History:
def set_contacts_manager(self, contacts_manager):
self._contacts_manager = contacts_manager
# -----------------------------------------------------------------------------------------------------------------
# History support
# -----------------------------------------------------------------------------------------------------------------
def save_history(self):
"""
@ -128,9 +126,7 @@ class History:
return generator.generate()
# -----------------------------------------------------------------------------------------------------------------
# Items creation
# -----------------------------------------------------------------------------------------------------------------
def _create_message_item(self, message):
return self._messages_items_factory.create_message_item(message, False)

View File

@ -192,8 +192,8 @@ def main_parser(_=None, iMode=2):
parser.add_argument('--auto_accept_path', '--auto-accept-path', type=str,
default=os.path.join(os.environ['HOME'], 'Downloads'),
help="auto_accept_path")
parser.add_argument('--mode', type=int, default=iMode,
help='Mode: 0=chat 1=chat+audio 2=chat+audio+video default: 0')
# parser.add_argument('--mode', type=int, default=iMode,
# help='Mode: 0=chat 1=chat+audio 2=chat+audio+video default: 0')
parser.add_argument('--font', type=str, default="Courier",
help='Message font')
parser.add_argument('--message_font_size', type=int, default=15,

View File

@ -38,9 +38,7 @@ class Messenger(tox_save.ToxSave):
return contact.get_last_message_text()
# -----------------------------------------------------------------------------------------------------------------
# Messaging - friends
# -----------------------------------------------------------------------------------------------------------------
def new_message(self, friend_number, message_type, message):
"""
@ -140,9 +138,7 @@ class Messenger(tox_save.ToxSave):
except Exception as ex:
LOG.warn('Sending pending messages failed with ' + str(ex))
# -----------------------------------------------------------------------------------------------------------------
# Messaging - groups
# -----------------------------------------------------------------------------------------------------------------
def send_message_to_group(self, text, message_type, group_number=None):
if group_number is None:
@ -183,9 +179,7 @@ class Messenger(tox_save.ToxSave):
text_message = TextMessage(message, MessageAuthor(peer.name, MESSAGE_AUTHOR['GC_PEER']), t, message_type)
self._add_message(text_message, group)
# -----------------------------------------------------------------------------------------------------------------
# Messaging - group peers
# -----------------------------------------------------------------------------------------------------------------
def send_message_to_group_peer(self, text, message_type, group_number=None, peer_id=None):
if group_number is None or peer_id is None:
@ -238,17 +232,13 @@ class Messenger(tox_save.ToxSave):
return
self._add_message(text_message, group_peer_contact)
# -----------------------------------------------------------------------------------------------------------------
# Message receipts
# -----------------------------------------------------------------------------------------------------------------
def receipt(self, friend_number, message_id):
friend = self._get_friend_by_number(friend_number)
friend.mark_as_sent(message_id)
# -----------------------------------------------------------------------------------------------------------------
# Typing notifications
# -----------------------------------------------------------------------------------------------------------------
def send_typing(self, typing):
"""
@ -266,9 +256,7 @@ class Messenger(tox_save.ToxSave):
if self._contacts_manager.is_friend_active(friend_number):
self._screen.typing.setVisible(typing)
# -----------------------------------------------------------------------------------------------------------------
# Contact info updated
# -----------------------------------------------------------------------------------------------------------------
def new_friend_name(self, friend, old_name, new_name):
if old_name == new_name or friend.has_alias():
@ -279,9 +267,7 @@ class Messenger(tox_save.ToxSave):
friend.actions = True
self._add_info_message(friend.number, message)
# -----------------------------------------------------------------------------------------------------------------
# Private methods
# -----------------------------------------------------------------------------------------------------------------
@staticmethod
def _split_message(message):

View File

@ -46,9 +46,7 @@ def bTooSoon(key, sSlot, fSec=10.0):
# TODO: refactoring. Use contact provider instead of manager
# -----------------------------------------------------------------------------------------------------------------
# Callbacks - current user
# -----------------------------------------------------------------------------------------------------------------
global iBYTES
iBYTES=0
@ -95,9 +93,7 @@ def self_connection_status(tox, profile):
return wrapped
# -----------------------------------------------------------------------------------------------------------------
# Callbacks - friends
# -----------------------------------------------------------------------------------------------------------------
def friend_status(contacts_manager, file_transfer_handler, profile, settings):
@ -235,9 +231,7 @@ def friend_read_receipt(messenger):
return wrapped
# -----------------------------------------------------------------------------------------------------------------
# Callbacks - file transfers
# -----------------------------------------------------------------------------------------------------------------
def tox_file_recv(window, tray, profile, file_transfer_handler, contacts_manager, settings):
@ -312,9 +306,7 @@ def file_recv_control(file_transfer_handler):
return wrapped
# -----------------------------------------------------------------------------------------------------------------
# Callbacks - custom packets
# -----------------------------------------------------------------------------------------------------------------
def lossless_packet(plugin_loader):
@ -339,9 +331,7 @@ def lossy_packet(plugin_loader):
return wrapped
# -----------------------------------------------------------------------------------------------------------------
# Callbacks - audio
# -----------------------------------------------------------------------------------------------------------------
def call_state(calls_manager):
def wrapped(iToxav, friend_number, mask, user_data):
@ -384,9 +374,7 @@ def callback_audio(calls_manager):
return wrapped
# -----------------------------------------------------------------------------------------------------------------
# Callbacks - video
# -----------------------------------------------------------------------------------------------------------------
def video_receive_frame(toxav, friend_number, width, height, y, u, v, ystride, ustride, vstride, user_data):
@ -444,9 +432,7 @@ def video_receive_frame(toxav, friend_number, width, height, y, u, v, ystride, u
LOG_ERROR(f"video_receive_frame {ex} #{friend_number}")
pass
# -----------------------------------------------------------------------------------------------------------------
# Callbacks - groups
# -----------------------------------------------------------------------------------------------------------------
def group_message(window, tray, tox, messenger, settings, profile):
@ -714,9 +700,7 @@ def group_privacy_state(contacts_provider):
return wrapped
# -----------------------------------------------------------------------------------------------------------------
# Callbacks - initialization
# -----------------------------------------------------------------------------------------------------------------
def init_callbacks(tox, profile, settings, plugin_loader, contacts_manager,

View File

@ -28,9 +28,7 @@ def LOG_TRACE(l): pass # print('TRACE+ '+l)
iLAST_CONN = 0
iLAST_DELTA = 60
# -----------------------------------------------------------------------------------------------------------------
# Base threads
# -----------------------------------------------------------------------------------------------------------------
class BaseThread(threading.Thread):
@ -74,9 +72,7 @@ class BaseQThread(QtCore.QThread):
else:
LOG_WARN(f"BaseQThread {self.name} BLOCKED")
# -----------------------------------------------------------------------------------------------------------------
# Toxcore threads
# -----------------------------------------------------------------------------------------------------------------
class InitThread(BaseThread):
@ -163,9 +159,7 @@ class ToxAVIterateThread(BaseQThread):
sleep(self._toxav.iteration_interval() / 1000)
# -----------------------------------------------------------------------------------------------------------------
# File transfers thread
# -----------------------------------------------------------------------------------------------------------------
class FileTransfersThread(BaseQThread):
@ -203,9 +197,7 @@ def execute(func, *args, **kwargs):
_thread.execute(func, *args, **kwargs)
# -----------------------------------------------------------------------------------------------------------------
# Invoking in main thread
# -----------------------------------------------------------------------------------------------------------------
class InvokeEvent(QtCore.QEvent):
EVENT_TYPE = QtCore.QEvent.Type(QtCore.QEvent.registerEventType())

27
toxygen/plugins/README.md Normal file
View File

@ -0,0 +1,27 @@
# Plugins
Repo with plugins for [Toxygen](https://macaw.me/emdee/toxygen/)
For more info visit [plugins.md](https://macaw.me/emdee/toxygen/blob/master/docs/plugins.md) and [plugin_api.md](https://github.com/toxygen-project[/toxygen/blob/master/docs/plugin-api.md)
# Plugins list:
- ToxId - share your Tox ID and copy friend's Tox ID easily.
- MarqueeStatus - create ticker from your status message.
- BirthDay - get notifications on your friends' birthdays.
- Bot - bot which can communicate with your friends when you are away.
- SearchPlugin - select text in message and find it in search engine.
- AutoAwayStatusLinux - sets "Away" status when user is inactive (Linux only).
- AutoAwayStatusWindows - sets "Away" status when user is inactive (Windows only).
- Chess - play chess with your friends using Tox.
- Garland - changes your status like it's garland.
- AutoAnswer - calls auto answering.
- uToxInlineSending - send inlines with the same name as uTox does.
- AvatarEncryption - encrypt all avatars using profile password
## Hard fork
Not all of these are working...
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!

83
toxygen/plugins/ae.py Normal file
View File

@ -0,0 +1,83 @@
# -*- mode: python; indent-tabs-mode: nil; py-indent-offset: 4; coding: utf-8 -*-
import plugin_super_class
import json
from user_data import settings
import os
from bootstrap.bootstrap import get_user_config_path
class AvatarEncryption(plugin_super_class.PluginSuperClass):
def __init__(self, *args):
super(AvatarEncryption, self).__init__('AvatarEncryption', 'ae', *args)
self._path = os.path.join(get_user_config_path(), 'avatars')
self._app = args[0]
self._profile = self._app._ms._profile
self._window = None
#was self._contacts = self._profile._contacts[:]
self._contacts = self._profile._contacts_provider.get_all_friends()
def get_description(self):
return QApplication.translate("AvatarEncryption", 'Encrypt all avatars using profile password.')
def close(self):
if not self._encrypt_save.has_password():
return
i, data = 1, {}
self.save_contact_avatar(data, self._profile, 0)
for friend in self._contacts:
self.save_contact_avatar(data, friend, i)
i += 1
self.save_settings(json.dumps(data))
def start(self):
if not self._encrypt_save.has_password():
return
data = json.loads(self.load_settings())
self.load_contact_avatar(data, self._profile)
for friend in self._contacts:
self.load_contact_avatar(data, friend)
self._profile.update()
def save_contact_avatar(self, data, contact, i):
tox_id = contact.tox_id[:64]
data[str(tox_id)] = str(i)
path = os.path.join(self._path, tox_id + '.png')
if os.path.isfile(path):
with open(path, 'rb') as fl:
avatar = fl.read()
encr_avatar = self._encrypt_save.pass_encrypt(avatar)
with open(os.path.join(self._path, self._settings.name + '_' + str(i) + '.png'), 'wb') as fl:
fl.write(encr_avatar)
os.remove(path)
def load_contact_avatar(self, data, contact):
tox_id = str(contact.tox_id[:64])
if tox_id not in data:
return
path = os.path.join(self._path, self._settings.name + '_' + data[tox_id] + '.png')
if os.path.isfile(path):
with open(path, 'rb') as fl:
avatar = fl.read()
decr_avatar = self._encrypt_save.pass_decrypt(avatar)
with open(os.path.join(self._path, str(tox_id) + '.png'), 'wb') as fl:
fl.write(decr_avatar)
os.remove(path)
contact.load_avatar()
def load_settings(self):
try:
with open(plugin_super_class.path_to_data(self._short_name) + self._settings.name + '.json', 'rb') as fl:
data = fl.read()
return str(self._encrypt_save.pass_decrypt(data), 'utf-8') if data else '{}'
except:
return '{}'
def save_settings(self, data):
try:
data = self._encrypt_save.pass_encrypt(bytes(data, 'utf-8'))
with open(plugin_super_class.path_to_data(self._short_name) + self._settings.name + '.json', 'wb') as fl:
fl.write(data)
except:
pass

111
toxygen/plugins/awayl.py Normal file
View File

@ -0,0 +1,111 @@
import plugin_super_class
import threading
import time
from PyQt5 import QtCore, QtWidgets
from subprocess import check_output
import json
class InvokeEvent(QtCore.QEvent):
EVENT_TYPE = QtCore.QEvent.Type(QtCore.QEvent.registerEventType())
def __init__(self, fn, *args, **kwargs):
QtCore.QEvent.__init__(self, InvokeEvent.EVENT_TYPE)
self.fn = fn
self.args = args
self.kwargs = kwargs
class Invoker(QtCore.QObject):
def event(self, event):
event.fn(*event.args, **event.kwargs)
return True
_invoker = Invoker()
def invoke_in_main_thread(fn, *args, **kwargs):
QtCore.QCoreApplication.postEvent(_invoker, InvokeEvent(fn, *args, **kwargs))
class AutoAwayStatusLinux(plugin_super_class.PluginSuperClass):
def __init__(self, *args):
super().__init__('AutoAwayStatusLinux', 'awayl', *args)
self._thread = None
self._exec = None
self._active = False
self._time = json.loads(self.load_settings())['time']
self._prev_status = 0
self._app = args[0]
self._profile=self._app._ms._profile
self._window = None
def get_description(self):
return QApplication.translate("AutoAwayStatusLinux", 'sets "Away" status when user is inactive (Linux only).')
def close(self):
self.stop()
def stop(self):
self._exec = False
if self._active:
self._thread.join()
def start(self):
self._exec = True
self._thread = threading.Thread(target=self.loop)
self._thread.start()
def save(self):
self.save_settings('{"time": ' + str(self._time) + '}')
def change_status(self, status=1):
if self._profile.status in (0, 2):
self._prev_status = self._profile.status
if status is not None:
invoke_in_main_thread(self._profile.set_status, status)
def get_window(self):
inst = self
class Window(QtWidgets.QWidget):
def __init__(self):
super(Window, self).__init__()
self.setGeometry(QtCore.QRect(450, 300, 350, 100))
self.label = QtWidgets.QLabel(self)
self.label.setGeometry(QtCore.QRect(20, 0, 310, 35))
self.label.setText(QtWidgets.QApplication.translate("AutoAwayStatusLinux", "Auto away time in minutes\n(0 - to disable)"))
self.time = QtWidgets.QLineEdit(self)
self.time.setGeometry(QtCore.QRect(20, 40, 310, 25))
self.time.setText(str(inst._time))
self.setWindowTitle("AutoAwayStatusLinux")
self.ok = QtWidgets.QPushButton(self)
self.ok.setGeometry(QtCore.QRect(20, 70, 310, 25))
self.ok.setText(
QtWidgets.QApplication.translate("AutoAwayStatusLinux", "Save"))
self.ok.clicked.connect(self.update)
def update(self):
try:
t = int(self.time.text())
except:
t = 0
inst._time = t
inst.save()
self.close()
return Window()
def loop(self):
self._active = True
while self._exec:
time.sleep(5)
d = check_output(['xprintidle'])
d = int(d) // 1000
if self._time:
if d > 60 * self._time:
self.change_status()
elif self._profile.status == 1:
self.change_status(self._prev_status)

View File

@ -0,0 +1,115 @@
import plugin_super_class
import threading
import time
from PyQt5 import QtCore, QtWidgets
from ctypes import Structure, windll, c_uint, sizeof, byref
import json
class LASTINPUTINFO(Structure):
_fields_ = [('cbSize', c_uint), ('dwTime', c_uint)]
def get_idle_duration():
lastInputInfo = LASTINPUTINFO()
lastInputInfo.cbSize = sizeof(lastInputInfo)
windll.user32.GetLastInputInfo(byref(lastInputInfo))
millis = windll.kernel32.GetTickCount() - lastInputInfo.dwTime
return millis / 1000.0
class InvokeEvent(QtCore.QEvent):
EVENT_TYPE = QtCore.QEvent.Type(QtCore.QEvent.registerEventType())
def __init__(self, fn, *args, **kwargs):
QtCore.QEvent.__init__(self, InvokeEvent.EVENT_TYPE)
self.fn = fn
self.args = args
self.kwargs = kwargs
class Invoker(QtCore.QObject):
def event(self, event):
event.fn(*event.args, **event.kwargs)
return True
_invoker = Invoker()
def invoke_in_main_thread(fn, *args, **kwargs):
QtCore.QCoreApplication.postEvent(_invoker, InvokeEvent(fn, *args, **kwargs))
class AutoAwayStatusWindows(plugin_super_class.PluginSuperClass):
def __init__(self, *args):
super().__init__('AutoAwayStatusWindows', 'awayw', *args)
self._thread = None
self._exec = None
self._active = False
self._time = json.loads(self.load_settings())['time']
self._prev_status = 0
def close(self):
self.stop()
def stop(self):
self._exec = False
if self._active:
self._thread.join()
def start(self):
self._exec = True
self._thread = threading.Thread(target=self.loop)
self._thread.start()
def save(self):
self.save_settings('{"time": ' + str(self._time) + '}')
def change_status(self, status=1):
if self._profile.status != 1:
self._prev_status = self._profile.status
invoke_in_main_thread(self._profile.set_status, status)
def get_window(self):
inst = self
class Window(QtWidgets.QWidget):
def __init__(self):
super(Window, self).__init__()
self.setGeometry(QtCore.QRect(450, 300, 350, 100))
self.label = QtWidgets.QLabel(self)
self.label.setGeometry(QtCore.QRect(20, 0, 310, 35))
self.label.setText(QtWidgets.QApplication.translate("AutoAwayStatusWindows", "Auto away time in minutes\n(0 - to disable)"))
self.time = QtWidgets.QLineEdit(self)
self.time.setGeometry(QtCore.QRect(20, 40, 310, 25))
self.time.setText(str(inst._time))
self.setWindowTitle("AutoAwayStatusWindows")
self.ok = QtWidgets.QPushButton(self)
self.ok.setGeometry(QtCore.QRect(20, 70, 310, 25))
self.ok.setText(
QtWidgets.QApplication.translate("AutoAwayStatusWindows", "Save"))
self.ok.clicked.connect(self.update)
def update(self):
try:
t = int(self.time.text())
except:
t = 0
inst._time = t
inst.save()
self.close()
return Window()
def loop(self):
self._active = True
while self._exec:
time.sleep(5)
d = get_idle_duration()
if self._time:
if d > 60 * self._time:
self.change_status()
elif self._profile.status == 1:
self.change_status(self._prev_status)

2
toxygen/plugins/bday.pro Normal file
View File

@ -0,0 +1,2 @@
SOURCES = bday.py
TRANSLATIONS = bday/en_GB.ts bday/en_US.ts bday/ru_RU.ts

95
toxygen/plugins/bday.py Normal file
View File

@ -0,0 +1,95 @@
import plugin_super_class
from PyQt5 import QtWidgets, QtCore
import json
import importlib
class BirthDay(plugin_super_class.PluginSuperClass):
def __init__(self, *args):
# Constructor. In plugin __init__ should take only 1 last argument
super(BirthDay, self).__init__('BirthDay', 'bday', *args)
self._data = json.loads(self.load_settings())
self._datetime = importlib.import_module('datetime')
self._timers = []
self._app = args[0]
self._profile=self._app._ms._profile
self._window = None
def start(self):
now = self._datetime.datetime.now()
today = {}
x = self._profile.tox_id[:64]
for key in self._data:
if key != x and key != 'send_date':
arr = self._data[key].split('.')
if int(arr[0]) == now.day and int(arr[1]) == now.month:
today[key] = now.year - int(arr[2])
if len(today):
msgbox = QtWidgets.QMessageBox()
title = QtWidgets.QApplication.translate('BirthDay', "Birthday!")
msgbox.setWindowTitle(title)
text = ', '.join(self._profile.get_friend_by_number(self._tox.friend_by_public_key(x)).name + ' ({})'.format(today[x]) for x in today)
msgbox.setText('Birthdays: ' + text)
msgbox.exec_()
def get_description(self):
return QApplication.translate("BirthDay", "Send and get notifications on your friends' birthdays.")
def get_window(self):
inst = self
x = self._profile.tox_id[:64]
class Window(QtWidgets.QWidget):
def __init__(self):
super(Window, self).__init__()
self.setGeometry(QtCore.QRect(450, 300, 350, 150))
self.send = QtWidgets.QCheckBox(self)
self.send.setGeometry(QtCore.QRect(20, 10, 310, 25))
self.send.setText(QtWidgets.QApplication.translate('BirthDay', "Send my birthday date to contacts"))
self.setWindowTitle(QtWidgets.QApplication.translate('BirthDay', "Birthday"))
self.send.clicked.connect(self.update)
self.send.setChecked(inst._data['send_date'])
self.date = QtWidgets.QLineEdit(self)
self.date.setGeometry(QtCore.QRect(20, 50, 310, 25))
self.date.setPlaceholderText(QtWidgets.QApplication.translate('BirthDay', "Date in format dd.mm.yyyy"))
self.set_date = QtWidgets.QPushButton(self)
self.set_date.setGeometry(QtCore.QRect(20, 90, 310, 25))
self.set_date.setText(QtWidgets.QApplication.translate('BirthDay', "Save date"))
self.set_date.clicked.connect(self.save_curr_date)
self.date.setText(inst._data[x] if x in inst._data else '')
def save_curr_date(self):
inst._data[x] = self.date.text()
inst.save_settings(json.dumps(inst._data))
self.close()
def update(self):
inst._data['send_date'] = self.send.isChecked()
inst.save_settings(json.dumps(inst._data))
if not hasattr(self, '_window') or not self._window:
self._window = Window()
return self._window
def lossless_packet(self, data, friend_number):
if len(data):
friend = self._profile.get_friend_by_number(friend_number)
self._data[friend.tox_id] = data
self.save_settings(json.dumps(self._data))
elif self._data['send_date'] and self._profile.tox_id[:64] in self._data:
self.send_lossless(self._data[self._profile.tox_id[:64]], friend_number)
def friend_connected(self, friend_number):
timer = QtCore.QTimer()
timer.timeout.connect(lambda: self.timer(friend_number))
timer.start(10000)
self._timers.append(timer)
def timer(self, friend_number):
timer = self._timers.pop()
timer.stop()
if self._profile.get_friend_by_number(friend_number).tox_id not in self._data:
self.send_lossless('', friend_number)

81
toxygen/plugins/bot.py Normal file
View File

@ -0,0 +1,81 @@
import plugin_super_class
from PyQt5 import QtCore
import time
class InvokeEvent(QtCore.QEvent):
EVENT_TYPE = QtCore.QEvent.Type(QtCore.QEvent.registerEventType())
def __init__(self, fn, *args, **kwargs):
QtCore.QEvent.__init__(self, InvokeEvent.EVENT_TYPE)
self.fn = fn
self.args = args
self.kwargs = kwargs
class Invoker(QtCore.QObject):
def event(self, event):
event.fn(*event.args, **event.kwargs)
return True
_invoker = Invoker()
def invoke_in_main_thread(fn, *args, **kwargs):
QtCore.QCoreApplication.postEvent(_invoker, InvokeEvent(fn, *args, **kwargs))
class Bot(plugin_super_class.PluginSuperClass):
def __init__(self, *args):
super(Bot, self).__init__('Bot', 'bot', *args)
self._callback = None
self._mode = 0
self._message = "I'm away, will back soon"
self._timer = QtCore.QTimer()
self._timer.timeout.connect(self.initialize)
self._app = args[0]
self._profile=self._app._ms._profile
self._window = None
def get_description(self):
return QApplication.translate("Bot", 'Plugin to answer bot to your friends.')
def start(self):
self._timer.start(10000)
def command(self, command):
if command.startswith('mode '):
self._mode = int(command.split(' ')[-1])
elif command.startswith('message '):
self._message = command[8:]
else:
super().command(command)
def initialize(self):
self._timer.stop()
self._callback = self._tox.friend_message_cb
def incoming_message(tox, friend_number, message_type, message, size, user_data):
self._callback(tox, friend_number, message_type, message, size, user_data)
if self._profile.status == 1: # TOX_USER_STATUS['AWAY']
self.answer(friend_number, str(message, 'utf-8'))
self._tox.callback_friend_message(incoming_message) # , None
def stop(self):
if not self._callback: return
try:
# TypeError: argument must be callable or integer function address
self._tox.callback_friend_message(self._callback) # , None
except: pass
def close(self):
self.stop()
def answer(self, friend_number, message):
if not self._mode:
message = self._message
invoke_in_main_thread(self._profile.send_message, message, friend_number)

1695
toxygen/plugins/chess.py Normal file

File diff suppressed because it is too large Load Diff

31
toxygen/plugins/en_GB.ts Normal file
View File

@ -0,0 +1,31 @@
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE TS><TS version="1.1">
<context>
<name>BirthDay</name>
<message>
<location filename="bday.py" line="28"/>
<source>Birthday!</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="bday.py" line="44"/>
<source>Send my birthday date to contacts</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="bday.py" line="45"/>
<source>Birthday</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="bday.py" line="50"/>
<source>Date in format dd.mm.yyyy</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="bday.py" line="53"/>
<source>Save date</source>
<translation type="unfinished"></translation>
</message>
</context>
</TS>

31
toxygen/plugins/en_US.ts Normal file
View File

@ -0,0 +1,31 @@
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE TS><TS version="1.1">
<context>
<name>BirthDay</name>
<message>
<location filename="bday.py" line="28"/>
<source>Birthday!</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="bday.py" line="44"/>
<source>Send my birthday date to contacts</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="bday.py" line="45"/>
<source>Birthday</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="bday.py" line="50"/>
<source>Date in format dd.mm.yyyy</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="bday.py" line="53"/>
<source>Save date</source>
<translation type="unfinished"></translation>
</message>
</context>
</TS>

View File

@ -0,0 +1,75 @@
import plugin_super_class
import threading
import time
from PyQt5 import QtCore
class InvokeEvent(QtCore.QEvent):
EVENT_TYPE = QtCore.QEvent.Type(QtCore.QEvent.registerEventType())
def __init__(self, fn, *args, **kwargs):
QtCore.QEvent.__init__(self, InvokeEvent.EVENT_TYPE)
self.fn = fn
self.args = args
self.kwargs = kwargs
class Invoker(QtCore.QObject):
def event(self, event):
event.fn(*event.args, **event.kwargs)
return True
_invoker = Invoker()
def invoke_in_main_thread(fn, *args, **kwargs):
QtCore.QCoreApplication.postEvent(_invoker, InvokeEvent(fn, *args, **kwargs))
class Garland(plugin_super_class.PluginSuperClass):
def __init__(self, *args):
super(Garland, self).__init__('Garland', 'grlnd', *args)
self._thread = None
self._exec = None
self._time = 3
self._app = args[0]
self._profile=self._app._ms._profile
self._window = None
def get_description(self):
return QApplication.translate("Garland", "Changes your status like it's garland.")
def close(self):
self.stop()
def stop(self):
self._exec = False
self._thread.join()
def start(self):
self._exec = True
self._thread = threading.Thread(target=self.change_status)
self._thread.start()
def command(self, command):
if command.startswith('time'):
self._time = max(int(command.split(' ')[1]), 300) / 1000
else:
super().command(command)
def update(self):
if hasattr(self, '_profile'):
if not hasattr(self._profile, 'status') or not self._profile.status:
retval = 0
else:
retval = (self._profile.status + 1) % 3
self._profile.set_status(retval)
def change_status(self):
time.sleep(5)
while self._exec:
invoke_in_main_thread(self.update)
time.sleep(self._time)

86
toxygen/plugins/mrq.py Normal file
View File

@ -0,0 +1,86 @@
# -*- mode: python; indent-tabs-mode: nil; py-indent-offset: 4; coding: utf-8 -*-
import plugin_super_class
import threading
import time
from PyQt5 import QtCore
class InvokeEvent(QtCore.QEvent):
EVENT_TYPE = QtCore.QEvent.Type(QtCore.QEvent.registerEventType())
def __init__(self, fn, *args, **kwargs):
QtCore.QEvent.__init__(self, InvokeEvent.EVENT_TYPE)
self.fn = fn
self.args = args
self.kwargs = kwargs
class Invoker(QtCore.QObject):
def event(self, event):
event.fn(*event.args, **event.kwargs)
return True
_invoker = Invoker()
def invoke_in_main_thread(fn, *args, **kwargs):
QtCore.QCoreApplication.postEvent(_invoker, InvokeEvent(fn, *args, **kwargs))
class MarqueeStatus(plugin_super_class.PluginSuperClass):
def __init__(self, *args):
super(MarqueeStatus, self).__init__('MarqueeStatus', 'mrq', *args)
self._thread = None
self._exec = None
self.active = False
self.left = True
self._app = args[0]
self._profile=self._app._ms._profile
self._window = None
def get_description(self):
return QApplication.translate("MarqueeStatus", 'Create ticker from your status message.')
def close(self):
self.stop()
def stop(self):
self._exec = False
if self.active:
self._thread.join()
def start(self):
self._exec = True
self._thread = threading.Thread(target=self.change_status)
self._thread.start()
def command(self, command):
if command == 'rev':
self.left = not self.left
else:
super(MarqueeStatus, self).command(command)
def set_status_message(self):
message = str(self._profile.status_message)
if self.left:
self._profile.set_status_message(bytes(message[1:] + message[0], 'utf-8'))
else:
self._profile.set_status_message(bytes(message[-1] + message[:-1], 'utf-8'))
def init_status(self):
self._profile.status_message = bytes(self._profile.status_message.strip() + ' ', 'utf-8')
def change_status(self):
self.active = True
if hasattr(self, '_profile'):
tmp = self._profile.status_message
time.sleep(10)
invoke_in_main_thread(self.init_status)
while self._exec:
time.sleep(1)
if self._profile.status is not None:
invoke_in_main_thread(self.set_status_message)
invoke_in_main_thread(self._profile.set_status_message, bytes(tmp, 'utf-8'))
self.active = False

View File

@ -53,9 +53,7 @@ class PluginSuperClass(tox_save.ToxSave):
self._short_name = short_name[:MAX_SHORT_NAME_LENGTH]
self._translator = None # translator for plugin's GUI
# -----------------------------------------------------------------------------------------------------------------
# Get methods
# -----------------------------------------------------------------------------------------------------------------
def get_name(self):
"""
@ -98,9 +96,7 @@ class PluginSuperClass(tox_save.ToxSave):
"""
return None
# -----------------------------------------------------------------------------------------------------------------
# Plugin was stopped, started or new command received
# -----------------------------------------------------------------------------------------------------------------
def start(self):
"""
@ -130,9 +126,7 @@ class PluginSuperClass(tox_save.ToxSave):
title = util_ui.tr('List of commands for plugin {}').format(self._name)
util_ui.message_box(text, title)
# -----------------------------------------------------------------------------------------------------------------
# Translations support
# -----------------------------------------------------------------------------------------------------------------
def load_translator(self):
"""
@ -149,9 +143,7 @@ class PluginSuperClass(tox_save.ToxSave):
self._translator.load(path_to_data(self._short_name) + lang_path)
app.installTranslator(self._translator)
# -----------------------------------------------------------------------------------------------------------------
# Settings loading and saving
# -----------------------------------------------------------------------------------------------------------------
def load_settings(self):
"""
@ -170,9 +162,7 @@ class PluginSuperClass(tox_save.ToxSave):
with open(path_to_data(self._short_name) + 'settings.json', 'wb') as fl:
fl.write(bytes(data, 'utf-8'))
# -----------------------------------------------------------------------------------------------------------------
# Callbacks
# -----------------------------------------------------------------------------------------------------------------
def lossless_packet(self, data, friend_number):
"""
@ -196,9 +186,7 @@ class PluginSuperClass(tox_save.ToxSave):
"""
pass
# -----------------------------------------------------------------------------------------------------------------
# Custom packets sending
# -----------------------------------------------------------------------------------------------------------------
def send_lossless(self, data, friend_number):
"""

BIN
toxygen/plugins/ru_RU.qm Normal file

Binary file not shown.

32
toxygen/plugins/ru_RU.ts Normal file
View File

@ -0,0 +1,32 @@
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE TS>
<TS version="2.0" language="ru_RU">
<context>
<name>BirthDay</name>
<message>
<location filename="bday.py" line="28"/>
<source>Birthday!</source>
<translation>День рождения!</translation>
</message>
<message>
<location filename="bday.py" line="44"/>
<source>Send my birthday date to contacts</source>
<translation>Отправлять дату моего рождения контактам</translation>
</message>
<message>
<location filename="bday.py" line="45"/>
<source>Birthday</source>
<translation>День рождения</translation>
</message>
<message>
<location filename="bday.py" line="50"/>
<source>Date in format dd.mm.yyyy</source>
<translation>Дата в формате дд.мм.гггг</translation>
</message>
<message>
<location filename="bday.py" line="53"/>
<source>Save date</source>
<translation>Сохранить дату</translation>
</message>
</context>
</TS>

2
toxygen/plugins/srch.pro Normal file
View File

@ -0,0 +1,2 @@
SOURCES = srch.py
TRANSLATIONS = srch/en_GB.ts srch/en_US.ts srch/ru_RU.ts

54
toxygen/plugins/srch.py Normal file
View File

@ -0,0 +1,54 @@
import plugin_super_class
from PyQt5 import QtGui, QtCore, QtWidgets
class SearchPlugin(plugin_super_class.PluginSuperClass):
def __init__(self, *args):
super(SearchPlugin, self).__init__('SearchPlugin', 'srch', *args)
def get_description(self):
return QApplication.translate("SearchPlugin", 'Plugin search with search engines.')
def get_message_menu(self, menu, text):
google = QtWidgets.QAction(
QtWidgets.QApplication.translate("srch", "Find in Google"),
menu)
google.triggered.connect(lambda: self.google(text))
duck = QtWidgets.QAction(
QtWidgets.QApplication.translate("srch", "Find in DuckDuckGo"),
menu)
duck.triggered.connect(lambda: self.duck(text))
yandex = QtWidgets.QAction(
QtWidgets.QApplication.translate("srch", "Find in Yandex"),
menu)
yandex.triggered.connect(lambda: self.yandex(text))
bing = QtWidgets.QAction(
QtWidgets.QApplication.translate("srch", "Find in Bing"),
menu)
bing.triggered.connect(lambda: self.bing(text))
return [duck, google, yandex, bing]
def google(self, text):
url = QtCore.QUrl('https://www.google.com/search?q=' + text)
self.open_url(url)
def duck(self, text):
url = QtCore.QUrl('https://duckduckgo.com/?q=' + text)
self.open_url(url)
def yandex(self, text):
url = QtCore.QUrl('https://yandex.com/search/?text=' + text)
self.open_url(url)
def bing(self, text):
url = QtCore.QUrl('https://www.bing.com/search?q=' + text)
self.open_url(url)
def open_url(self, url):
QtGui.QDesktopServices.openUrl(url)

View File

@ -0,0 +1,2 @@
SOURCES = toxid.py
TRANSLATIONS = toxid/en_GB.ts toxid/en_US.ts toxid/ru_RU.ts

136
toxygen/plugins/toxid.py Normal file
View File

@ -0,0 +1,136 @@
import plugin_super_class
from PyQt5 import QtCore, QtWidgets
import json
class CopyableToxId(plugin_super_class.PluginSuperClass):
def __init__(self, *args):
super(CopyableToxId, self).__init__('CopyableToxId', 'toxid', *args)
self._data = json.loads(self.load_settings())
self._copy = False
self._curr = -1
self._timer = QtCore.QTimer()
self._timer.timeout.connect(lambda: self.timer())
self.load_translator()
self._app = args[0]
self._profile=self._app._ms._profile
self._window = None
def get_description(self):
return QtWidgets.QApplication.translate("TOXID", 'Plugin which allows you to copy TOX ID of your friends easily.')
def get_window(self):
inst = self
class Window(QtWidgets.QWidget):
def __init__(self):
super(Window, self).__init__()
self.setGeometry(QtCore.QRect(450, 300, 350, 100))
self.send = QtWidgets.QCheckBox(self)
self.send.setGeometry(QtCore.QRect(20, 10, 310, 25))
self.send.setText(QtWidgets.QApplication.translate("TOXID", "Send my TOX ID to contacts"))
self.setWindowTitle(QtWidgets.QApplication.translate("TOXID", "CopyableToxID"))
self.send.clicked.connect(self.update)
self.send.setChecked(inst._data['send_id'])
self.help = QtWidgets.QPushButton(self)
self.help.setGeometry(QtCore.QRect(20, 40, 200, 25))
self.help.setText(QtWidgets.QApplication.translate("TOXID", "List of commands"))
self.help.clicked.connect(lambda: inst.command('help'))
def update(self):
inst._data['send_id'] = self.send.isChecked()
inst.save_settings(json.dumps(inst._data))
if not hasattr(self, '_window') or not self._window:
self._window = Window()
return self._window
def lossless_packet(self, data, friend_number):
if len(data):
self._data['id'] = list(filter(lambda x: not x.startswith(data[:64]), self._data['id']))
self._data['id'].append(data)
if self._copy:
self._timer.stop()
self._copy = False
clipboard = QtWidgets.QApplication.clipboard()
clipboard.setText(data)
self.save_settings(json.dumps(self._data))
elif self._data['send_id']:
self.send_lossless(self._tox.self_get_address(), friend_number)
def error(self):
msgbox = QtWidgets.QMessageBox()
title = QtWidgets.QApplication.translate("TOXID", "Error")
msgbox.setWindowTitle(title.format(self._name))
text = QtWidgets.QApplication.translate("TOXID", "Tox ID cannot be copied")
msgbox.setText(text)
msgbox.exec_()
def timer(self):
self._copy = False
if self._curr + 1:
public_key = self._tox.friend_get_public_key(self._curr)
self._curr = -1
arr = list(filter(lambda x: x.startswith(public_key), self._data['id']))
if len(arr):
clipboard = QtWidgets.QApplication.clipboard()
clipboard.setText(arr[0])
else:
self.error()
else:
self.error()
self._timer.stop()
def friend_connected(self, friend_number):
self.send_lossless('', friend_number)
def command(self, text):
if text == 'copy':
num = self._profile.get_active_number()
if num == -1:
return
elif text.startswith('copy '):
num = int(text[5:])
if num < 0:
return
elif text == 'enable':
self._copy = True
return
elif text == 'disable':
self._copy = False
return
elif text == 'help':
msgbox = QtWidgets.QMessageBox()
title = QtWidgets.QApplication.translate("TOXID", "List of commands for plugin CopyableToxID")
msgbox.setWindowTitle(title)
text = QtWidgets.QApplication.translate("TOXID", """Commands:
copy: copy TOX ID of current friend
copy <friend_number>: copy TOX ID of friend with specified number
enable: allow send your TOX ID to friends
disable: disallow send your TOX ID to friends
help: show this help""")
msgbox.setText(text)
msgbox.exec_()
return
else:
return
public_key = self._tox.friend_get_public_key(num)
arr = list(filter(lambda x: x.startswith(public_key), self._data['id']))
if self._profile.get_friend_by_number(num).status is None and len(arr):
clipboard = QtWidgets.QApplication.clipboard()
clipboard.setText(arr[0])
elif self._profile.get_friend_by_number(num).status is not None:
self._copy = True
self._curr = num
self.send_lossless('', num)
self._timer.start(2000)
else:
self.error()
def get_menu(self, menu, num):
act = QtWidgets.QAction(QtWidgets.QApplication.translate("TOXID", "Copy TOX ID"), menu)
friend = self._profile.get_friend(num)
act.connect(act, QtCore.SIGNAL("triggered()"), lambda: self.command('copy ' + str(friend.number)))
return [act]

87
toxygen/tests/README.md Normal file
View File

@ -0,0 +1,87 @@
# toxygen_wrapper
[ctypes](https://docs.python.org/3/library/ctypes.html)
wrapping of [Tox](https://tox.chat/)
[```libtoxcore```](https://github.com/TokTok/c-toxcore) into Python.
Taken from the ```wrapper``` directory of the now abandoned
<https://github.com/toxygen-project/toxygen> ```next_gen``` branch
by Ingvar.
The basics of NGC groups are supported, as well as AV and toxencryptsave.
There is no coverage of conferences as they are not used in ```toxygen```
and the list of still unwrapped calls as of Sept. 2022 can be found in
```tox.c-toxcore.missing```. The code still needs double-checking
that every call in ```tox.py``` has the right signature, but it runs
```toxygen``` with no apparent issues.
It has been tested with UDP and TCP proxy (Tor). It has ***not*** been
tested on Windows, and there may be some minor breakage, which should be
easy to fix. There is a good coverage integration testsuite in ```wrapper_tests```.
Change to that directory and run ```tests_wrapper.py --help```; the test
suite gives a good set of examples of usage.
## Install
Put the parent of the wrapper directory on your PYTHONPATH and
touch a file called `__init__.py` in its parent directory.
Then you need a ```libs``` directory beside the `wrapper` directory
and you need to link your ```libtoxcore.so``` and ```libtoxav.so```
and ```libtoxencryptsave.so``` into it. Link all 3 filenames
to ```libtoxcore.so``` if you have only ```libtoxcore.so```
(which is usually the case if you built ```c-toxcore``` with ```cmake```
rather than ```autogen/configure```). If you want to be different,
the environment variable TOXCORE_LIBS overrides the location of ```libs```.
As is, the code in ```tox.py``` is very verbose. Edit the file to change
```
def LOG_ERROR(a): print('EROR> '+a)
def LOG_WARN(a): print('WARN> '+a)
def LOG_INFO(a): print('INFO> '+a)
def LOG_DEBUG(a): print('DBUG> '+a)
def LOG_TRACE(a): pass # print('TRAC> '+a)
```
to all ```pass #``` or use ```logging.logger``` to suite your tastes.
```logging.logger``` can be dangerous in callbacks in ```Qt``` applications,
so we use simple print statements as default. The same applies to
```wrapper/tests_wrapper.py```.
## Prerequisites
No prerequisites in Python3.
## Other wrappers
There are a number of other wrappings into Python of Tox core.
This one uses [ctypes](https://docs.python.org/3/library/ctypes.html)
which has its merits - there is no need to recompile anything as with
Cython - change the Python file and it's done. And you can follow things
in a Python debugger, or with the utterly stupendous Python feature of
```gdb``` (```gdb -ex r --args /usr/bin/python3.9 <pyfile>```).
CTYPES code can be brittle, segfaulting if you've got things wrong,
but if your wrapping is right, it is very efficient and easy to work on.
The [faulthandler](https://docs.python.org/3/library/faulthandler.html)
module can be helpful in debugging crashes
(e.g. from segmentation faults produced by erroneous C library wrapping).
Others include:
* <https://github.com/TokTok/py-toxcore-c> Cython bindings.
Incomplete and not really actively supported. Maybe it will get
worked on in the future, but TokTok seems to be working on
java, rust, scalla, go, etc. bindings instead.
No support for NGC groups or toxencryptsave.
* <https://github.com/oxij/PyTox>
forked from https://github.com/aitjcize/PyTox
by Wei-Ning Huang <aitjcize@gmail.com>.
Hardcore C wrapping which is not easy to keep up to date.
No support for NGC or toxencryptsave. Abandonned.
This was the basis for the TokTok/py-toxcore-c code until recently.
To our point of view, the ability of CTYPEs to follow code in the
debugger is a crucial advantage.
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!

View File

View File

@ -0,0 +1,151 @@
if False:
@unittest.skip # to yet
def test_conference(self):
"""
t:group_new
t:conference_delete
t:conference_get_chatlist_size
t:conference_get_chatlist
t:conference_send_message
"""
bob_addr = self.bob.self_get_address()
alice_addr = self.alice.self_get_address()
self.abid = self.alice.friend_by_public_key(bob_addr)
self.baid = self.bob.friend_by_public_key(alice_addr)
assert self.bob_just_add_alice_as_friend()
#: Test group add
privacy_state = enums.TOX_GROUP_PRIVACY_STATE['PUBLIC']
group_name = 'test_group'
nick = 'test_nick'
status = None # dunno
self.group_id = self.bob.group_new(privacy_state, group_name, nick, status)
# :return group number on success, UINT32_MAX on failure.
assert self.group_id >= 0
self.loop(50)
BID = self.abid
def alices_on_conference_invite(self, fid, type_, data):
assert fid == BID
assert type_ == 0
gn = self.conference_join(fid, data)
assert type_ == self.conference_get_type(gn)
self.gi = True
def alices_on_conference_peer_list_changed(self, gid):
logging.debug("alices_on_conference_peer_list_changed")
assert gid == self.group_id
self.gn = True
try:
AliceTox.on_conference_invite = alices_on_conference_invite
AliceTox.on_conference_peer_list_changed = alices_on_conference_peer_list_changed
self.alice.gi = False
self.alice.gn = False
self.wait_ensure_exec(self.bob.conference_invite, (self.aid, self.group_id))
assert self.wait_callback_trues(self.alice, ['gi', 'gn'])
except AssertionError as e:
raise
finally:
AliceTox.on_conference_invite = Tox.on_conference_invite
AliceTox.on_conference_peer_list_change = Tox.on_conference_peer_list_changed
#: Test group number of peers
self.loop(50)
assert self.bob.conference_peer_count(self.group_id) == 2
#: Test group peername
self.alice.self_set_name('Alice')
self.bob.self_set_name('Bob')
def alices_on_conference_peer_list_changed(self, gid):
logging.debug("alices_on_conference_peer_list_changed")
self.gn = True
try:
AliceTox.on_conference_peer_list_changed = alices_on_conference_peer_list_changed
self.alice.gn = False
assert self.wait_callback_true(self.alice, 'gn')
except AssertionError as e:
raise
finally:
AliceTox.on_conference_peer_list_changed = Tox.on_conference_peer_list_changed
peernames = [self.bob.conference_peer_get_name(self.group_id, i) for i in
range(self.bob.conference_peer_count(self.group_id))]
assert 'Alice' in peernames
assert 'Bob' in peernames
#: Test title change
self.bob.conference_set_title(self.group_id, 'My special title')
assert self.bob.conference_get_title(self.group_id) == 'My special title'
#: Test group message
AID = self.aid
BID = self.bid
MSG = 'Group message test'
def alices_on_conference_message(self, gid, fgid, msg_type, message):
logging.debug("alices_on_conference_message" +repr(message))
if fgid == AID:
assert gid == self.group_id
assert str(message, 'UTF-8') == MSG
self.alice.gm = True
try:
AliceTox.on_conference_message = alices_on_conference_message
self.alice.gm = False
self.wait_ensure_exec(self.bob.conference_send_message, (
self.group_id, TOX_MESSAGE_TYPE['NORMAL'], MSG))
assert self.wait_callback_true(self.alice, 'gm')
except AssertionError as e:
raise
finally:
AliceTox.on_conference_message = Tox.on_conference_message
#: Test group action
AID = self.aid
BID = self.bid
MSG = 'Group action test'
def on_conference_action(self, gid, fgid, msg_type, action):
if fgid == AID:
assert gid == self.group_id
assert msg_type == TOX_MESSAGE_TYPE['ACTION']
assert str(action, 'UTF-8') == MSG
self.ga = True
try:
AliceTox.on_conference_message = on_conference_action
self.alice.ga = False
self.wait_ensure_exec(self.bob.conference_send_message,
(self.group_id, TOX_MESSAGE_TYPE['ACTION'], MSG))
assert self.wait_callback_true(self.alice, 'ga')
#: Test chatlist
assert len(self.bob.conference_get_chatlist()) == self.bob.conference_get_chatlist_size(), \
print(len(self.bob.conference_get_chatlist()), '!=', self.bob.conference_get_chatlist_size())
assert len(self.alice.conference_get_chatlist()) == self.bob.conference_get_chatlist_size(), \
print(len(self.alice.conference_get_chatlist()), '!=', self.bob.conference_get_chatlist_size())
assert self.bob.conference_get_chatlist_size() == 1, \
self.bob.conference_get_chatlist_size()
self.bob.conference_delete(self.group_id)
assert self.bob.conference_get_chatlist_size() == 0, \
self.bob.conference_get_chatlist_size()
except AssertionError as e:
raise
finally:
AliceTox.on_conference_message = Tox.on_conference_message

View File

@ -0,0 +1,439 @@
#!/var/local/bin/python3.bash
#
""" echo.py features
- accept friend request
- echo back friend message
- accept and answer friend call request
- send back friend audio/video data
- send back files friend sent
"""
from __future__ import print_function
import sys
import os
import traceback
import random
from ctypes import *
import argparse
import time
from os.path import exists
# LOG=util.log
global LOG
import logging
# log = lambda x: LOG.info(x)
LOG = logging.getLogger('app')
def LOG_error(a): print('EROR_ '+a)
def LOG_warn(a): print('WARN_ '+a)
def LOG_info(a): print('INFO_ '+a)
def LOG_debug(a): print('DBUG_ '+a)
def LOG_trace(a): pass # print('TRAC_ '+a)
from middleware.tox_factory import tox_factory
import wrapper
import wrapper.toxcore_enums_and_consts as enums
from wrapper.toxcore_enums_and_consts import TOX_CONNECTION, TOX_USER_STATUS, \
TOX_MESSAGE_TYPE, TOX_PUBLIC_KEY_SIZE, TOX_FILE_CONTROL
import user_data
from wrapper.libtox import LibToxCore
import wrapper_tests.support_testing as ts
from wrapper_tests.support_testing import oMainArgparser
from wrapper_tests.support_testing import logging_toxygen_echo
def sleep(fSec):
if 'QtCore' in globals():
if fSec > .000001: globals['QtCore'].QThread.msleep(fSec)
globals['QtCore'].QCoreApplication.processEvents()
else:
time.sleep(fSec)
try:
import coloredlogs
if 'COLOREDLOGS_LEVEL_STYLES' not in os.environ:
os.environ['COLOREDLOGS_LEVEL_STYLES'] = 'spam=22;debug=28;verbose=34;notice=220;warning=202;success=118,bold;error=124;critical=background=red'
except ImportError as e:
# logging.log(logging.DEBUG, f"coloredlogs not available: {e}")
coloredlogs = None
import wrapper_tests.support_testing as ts
if 'USER' in os.environ:
sDATA_FILE = '/tmp/logging_toxygen_' +os.environ['USER'] +'.tox'
elif 'USERNAME' in os.environ:
sDATA_FILE = '/tmp/logging_toxygen_' +os.environ['USERNAME'] +'.tox'
else:
sDATA_FILE = '/tmp/logging_toxygen_' +'data' +'.tox'
bHAVE_AV = True
iDHT_TRIES = 100
iDHT_TRY = 0
#?SERVER = lLOCAL[-1]
class AV(wrapper.tox.ToxAV):
def __init__(self, core):
super(AV, self).__init__(core)
self.core = self.get_tox()
def on_call(self, fid, audio_enabled, video_enabled):
LOG.info("Incoming %s call from %d:%s ..." % (
"video" if video_enabled else "audio", fid,
self.core.friend_get_name(fid)))
bret = self.answer(fid, 48, 64)
LOG.info(f"Answered, in call... {bret!s}")
def on_call_state(self, fid, state):
LOG.info('call state:fn=%d, state=%d' % (fid, state))
def on_audio_bit_rate(self, fid, audio_bit_rate):
LOG.info('audio bit rate status: fn=%d, abr=%d' %
(fid, audio_bit_rate))
def on_video_bit_rate(self, fid, video_bit_rate):
LOG.info('video bit rate status: fn=%d, vbr=%d' %
(fid, video_bit_rate))
def on_audio_receive_frame(self, fid, pcm, sample_count,
channels, sampling_rate):
# LOG.info('audio frame: %d, %d, %d, %d' %
# (fid, sample_count, channels, sampling_rate))
# LOG.info('pcm len:%d, %s' % (len(pcm), str(type(pcm))))
sys.stdout.write('.')
sys.stdout.flush()
bret = self.audio_send_frame(fid, pcm, sample_count,
channels, sampling_rate)
if bret is False:
LOG.error('on_audio_receive_frame error.')
def on_video_receive_frame(self, fid, width, height, frame, u, v):
LOG.info('video frame: %d, %d, %d, ' % (fid, width, height))
sys.stdout.write('*')
sys.stdout.flush()
bret = self.video_send_frame(fid, width, height, frame, u, v)
if bret is False:
LOG.error('on_video_receive_frame error.')
def witerate(self):
self.iterate()
def save_to_file(tox, fname):
data = tox.get_savedata()
with open(fname, 'wb') as f:
f.write(data)
def load_from_file(fname):
assert os.path.exists(fname)
return open(fname, 'rb').read()
class EchoBot():
def __init__(self, oTox):
self._tox = oTox
self._tox.self_set_name("EchoBot")
LOG.info('ID: %s' % self._tox.self_get_address())
self.files = {}
self.av = None
self.on_connection_status = None
def start(self):
self.connect()
if bHAVE_AV:
# RuntimeError: Attempted to create a second session for the same Tox instance.
self.av = True # AV(self._tox_pointer)
def bobs_on_friend_request(iTox,
public_key,
message_data,
message_data_size,
*largs):
key = ''.join(chr(x) for x in public_key[:TOX_PUBLIC_KEY_SIZE])
sPk = wrapper.tox.bin_to_string(key, TOX_PUBLIC_KEY_SIZE)
sMd = str(message_data, 'UTF-8')
LOG.debug('on_friend_request ' +sPk +' ' +sMd)
self.on_friend_request(sPk, sMd)
LOG.info('setting bobs_on_friend_request')
self._tox.callback_friend_request(bobs_on_friend_request)
def bobs_on_friend_message(iTox,
iFriendNum,
iMessageType,
message_data,
message_data_size,
*largs):
sMd = str(message_data, 'UTF-8')
LOG_debug(f"on_friend_message {iFriendNum}" +' ' +sMd)
self.on_friend_message(iFriendNum, iMessageType, sMd)
LOG.info('setting bobs_on_friend_message')
self._tox.callback_friend_message(bobs_on_friend_message)
def bobs_on_file_chunk_request(iTox, fid, filenumber, position, length, *largs):
if length == 0:
return
data = self.files[(fid, filenumber)]['f'][position:(position + length)]
self._tox.file_send_chunk(fid, filenumber, position, data)
self._tox.callback_file_chunk_request(bobs_on_file_chunk_request)
def bobs_on_file_recv(iTox, fid, filenumber, kind, size, filename, *largs):
LOG_info(f"on_file_recv {fid!s} {filenumber!s} {kind!s} {size!s} {filename}")
if size == 0:
return
self.files[(fid, filenumber)] = {
'f': bytes(),
'filename': filename,
'size': size
}
self._tox.file_control(fid, filenumber, TOX_FILE_CONTROL['RESUME'])
def connect(self):
if not self.on_connection_status:
def on_connection_status(iTox, iCon, *largs):
LOG_info('ON_CONNECTION_STATUS - CONNECTED ' + repr(iCon))
self._tox.callback_self_connection_status(on_connection_status)
LOG.info('setting on_connection_status callback ')
self.on_connection_status = on_connection_status
if self._oargs.network in ['newlocal', 'local']:
LOG.info('connecting on the new network ')
sNet = 'newlocal'
elif self._oargs.network == 'new':
LOG.info('connecting on the new network ')
sNet = 'new'
else: # main old
LOG.info('connecting on the old network ')
sNet = 'old'
sFile = self._oargs.nodes_json
lNodes = generate_nodes_from_file(sFile)
lElts = lNodes
random.shuffle(lElts)
for lElt in lElts[:10]:
status = self._tox.self_get_connection_status()
try:
if self._tox.bootstrap(*lElt):
LOG.info('connected to ' + lElt[0]+' '+repr(status))
else:
LOG.warn('failed connecting to ' + lElt[0])
except Exception as e:
LOG.warn('error connecting to ' + lElt[0])
if self._oargs.proxy_type > 0:
random.shuffle(ts.lRELAYS)
for lElt in ts.lRELAYS[:10]:
status = self._tox.self_get_connection_status()
try:
if self._tox.add_tcp_relay(*lElt):
LOG.info('relayed to ' + lElt[0] +' '+repr(status))
else:
LOG.warn('failed relay to ' + lElt[0])
except Exception as e:
LOG.warn('error relay to ' + lElt[0])
def loop(self):
if not self.av:
self.start()
checked = False
save_to_file(self._tox, sDATA_FILE)
LOG.info('Starting loop.')
while True:
status = self._tox.self_get_connection_status()
if not checked and status:
LOG.info('Connected to DHT.')
checked = True
if not checked and not status:
global iDHT_TRY
iDHT_TRY += 10
self.connect()
self.iterate(100)
if iDHT_TRY >= iDHT_TRIES:
raise RuntimeError("Failed to connect to the DHT.")
LOG.warn(f"NOT Connected to DHT. {iDHT_TRY}")
checked = True
if checked and not status:
LOG.info('Disconnected from DHT.')
self.connect()
checked = False
if bHAVE_AV:
True # self.av.witerate()
self.iterate(100)
LOG.info('Ending loop.')
def iterate(self, n=100):
interval = self._tox.iteration_interval()
for i in range(n):
self._tox.iterate()
sleep(interval / 1000.0)
self._tox.iterate()
def on_friend_request(self, pk, message):
LOG.debug('Friend request from %s: %s' % (pk, message))
self._tox.friend_add_norequest(pk)
LOG.info('on_friend_request Accepted.')
save_to_file(self._tox, sDATA_FILE)
def on_friend_message(self, friendId, type, message):
name = self._tox.friend_get_name(friendId)
LOG.debug('%s: %s' % (name, message))
yMessage = bytes(message, 'UTF-8')
self._tox.friend_send_message(friendId, TOX_MESSAGE_TYPE['NORMAL'], yMessage)
LOG.info('EchoBot sent: %s' % message)
def on_file_recv_chunk(self, fid, filenumber, position, data):
filename = self.files[(fid, filenumber)]['filename']
size = self.files[(fid, filenumber)]['size']
LOG.debug(f"on_file_recv_chunk {fid!s} {filenumber!s} {filename} {position/float(size)*100!s}")
if data is None:
msg = "I got '{}', sending it back right away!".format(filename)
self._tox.friend_send_message(fid, TOX_MESSAGE_TYPE['NORMAL'], msg)
self.files[(fid, 0)] = self.files[(fid, filenumber)]
length = self.files[(fid, filenumber)]['size']
self.file_send(fid, 0, length, filename, filename)
del self.files[(fid, filenumber)]
return
self.files[(fid, filenumber)]['f'] += data
def iMain(oArgs):
global sDATA_FILE
# oTOX_OPTIONS = ToxOptions()
global oTOX_OPTIONS
oTOX_OPTIONS = oToxygenToxOptions(oArgs)
opts = oTOX_OPTIONS
if coloredlogs:
coloredlogs.install(
level=oArgs.loglevel,
logger=LOG,
# %(asctime)s,%(msecs)03d %(hostname)s [%(process)d]
fmt='%(name)s %(levelname)s %(message)s'
)
else:
if 'logfile' in oArgs:
logging.basicConfig(filename=oArgs.logfile,
level=oArgs.loglevel,
format='%(levelname)-8s %(message)s')
else:
logging.basicConfig(level=oArgs.loglevel,
format='%(levelname)-8s %(message)s')
iRet = 0
if hasattr(oArgs,'profile') and oArgs.profile and os.path.isfile(oArgs.profile):
sDATA_FILE = oArgs.profile
LOG.info(f"loading from {sDATA_FILE}")
opts.savedata_data = load_from_file(sDATA_FILE)
opts.savedata_length = len(opts.savedata_data)
opts.savedata_type = enums.TOX_SAVEDATA_TYPE['TOX_SAVE']
else:
opts.savedata_data = None
try:
if False:
oTox = tox_factory(data=opts.savedata_data,
settings=opts, args=oArgs, app=None)
else:
oTox = wrapper.tox.Tox(opts)
t = EchoBot(oTox)
t._oargs = oArgs
t.start()
t.loop()
save_to_file(t._tox, sDATA_FILE)
except KeyboardInterrupt:
save_to_file(t._tox, sDATA_FILE)
except RuntimeError as e:
LOG.error(f"exiting with {e}")
iRet = 1
except Exception as e:
LOG.error(f"exiting with {e}")
LOG.warn(' iMain(): ' \
+'\n' + traceback.format_exc())
iRet = 1
return iRet
def oToxygenToxOptions(oArgs, data=None):
tox_options = wrapper.tox.Tox.options_new()
tox_options.contents.local_discovery_enabled = False
tox_options.contents.dht_announcements_enabled = False
tox_options.contents.hole_punching_enabled = False
tox_options.contents.experimental_thread_safety = False
tox_options.contents.ipv6_enabled = False
tox_options.contents.tcp_port = 3390
if oArgs.proxy_type > 0:
tox_options.contents.proxy_type = int(oArgs.proxy_type)
tox_options.contents.proxy_host = bytes(oArgs.proxy_host, 'UTF-8')
tox_options.contents.proxy_port = int(oArgs.proxy_port)
tox_options.contents.udp_enabled = False
LOG.debug('setting oArgs.proxy_host = ' +oArgs.proxy_host)
else:
tox_options.contents.udp_enabled = True
if data: # load existing profile
tox_options.contents.savedata_type = enums.TOX_SAVEDATA_TYPE['TOX_SAVE']
tox_options.contents.savedata_data = c_char_p(data)
tox_options.contents.savedata_length = len(data)
else: # create new profile
tox_options.contents.savedata_type = enums.TOX_SAVEDATA_TYPE['NONE']
tox_options.contents.savedata_data = None
tox_options.contents.savedata_length = 0
if tox_options._options_pointer:
ts.vAddLoggerCallback(tox_options, ts.on_log)
else:
logging.warn("No tox_options._options_pointer " +repr(tox_options._options_pointer))
return tox_options
def oArgparse(lArgv):
parser = ts.oMainArgparser()
parser.add_argument('profile', type=str, nargs='?',
default=sDATA_FILE,
help='Path to Tox profile to save')
oArgs = parser.parse_args(lArgv)
if hasattr(oArgs, 'sleep') and oArgs.sleep == 'qt':
pass # broken
else:
oArgs.sleep = 'time'
for key in ts.lBOOLEANS:
if key not in oArgs: continue
val = getattr(oArgs, key)
if val in ['False', 'false', 0]:
setattr(oArgs, key, False)
else:
setattr(oArgs, key, True)
if not os.path.exists('/proc/sys/net/ipv6') and oArgs.ipv6_enabled:
LOG.warn('setting oArgs.ipv6_enabled = False')
oArgs.ipv6_enabled = False
return oArgs
def main(largs=None):
if largs is None: largs = []
oArgs = oArgparse(largs)
global oTOX_OARGS
oTOX_OARGS = oArgs
print(oArgs)
if coloredlogs:
logger = logging.getLogger()
# https://pypi.org/project/coloredlogs/
coloredlogs.install(level=oArgs.loglevel,
logger=logger,
# %(asctime)s,%(msecs)03d %(hostname)s [%(process)d]
fmt='%(name)s %(levelname)s %(message)s'
)
else:
logging.basicConfig(level=oArgs.loglevel) # logging.INFO
return iMain(oArgs)
if __name__ == '__main__':
sys.exit(main(sys.argv[1:]))

391
toxygen/tests/socks.py Normal file
View File

@ -0,0 +1,391 @@
"""SocksiPy - Python SOCKS module.
Version 1.00
Copyright 2006 Dan-Haim. All rights reserved.
Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
3. Neither the name of Dan Haim nor the names of his contributors may be used
to endorse or promote products derived from this software without specific
prior written permission.
THIS SOFTWARE IS PROVIDED BY DAN HAIM "AS IS" AND ANY EXPRESS OR IMPLIED
WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
EVENT SHALL DAN HAIM OR HIS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA
OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMANGE.
This module provides a standard socket-like interface for Python
for tunneling connections through SOCKS proxies.
"""
"""
Minor modifications made by Christopher Gilbert (http://motomastyle.com/)
for use in PyLoris (http://pyloris.sourceforge.net/)
Minor modifications made by Mario Vilas (http://breakingcode.wordpress.com/)
mainly to merge bug fixes found in Sourceforge
Minor modifications made by Eugene Dementiev (http://www.dementiev.eu/)
"""
import socket
import struct
import sys
PROXY_TYPE_SOCKS4 = 1
PROXY_TYPE_SOCKS5 = 2
PROXY_TYPE_HTTP = 3
_defaultproxy = None
_orgsocket = socket.socket
class ProxyError(Exception): pass
class GeneralProxyError(ProxyError): pass
class Socks5AuthError(ProxyError): pass
class Socks5Error(ProxyError): pass
class Socks4Error(ProxyError): pass
class HTTPError(ProxyError): pass
_generalerrors = ("success",
"invalid data",
"not connected",
"not available",
"bad proxy type",
"bad input")
_socks5errors = ("succeeded",
"general SOCKS server failure",
"connection not allowed by ruleset",
"Network unreachable",
"Host unreachable",
"Connection refused",
"TTL expired",
"Command not supported",
"Address type not supported",
"Unknown error")
_socks5autherrors = ("succeeded",
"authentication is required",
"all offered authentication methods were rejected",
"unknown username or invalid password",
"unknown error")
_socks4errors = ("request granted",
"request rejected or failed",
"request rejected because SOCKS server cannot connect to identd on the client",
"request rejected because the client program and identd report different user-ids",
"unknown error")
def setdefaultproxy(proxytype=None, addr=None, port=None, rdns=True, username=None, password=None):
"""setdefaultproxy(proxytype, addr[, port[, rdns[, username[, password]]]])
Sets a default proxy which all further socksocket objects will use,
unless explicitly changed.
"""
global _defaultproxy
_defaultproxy = (proxytype, addr, port, rdns, username, password)
def wrapmodule(module):
"""wrapmodule(module)
Attempts to replace a module's socket library with a SOCKS socket. Must set
a default proxy using setdefaultproxy(...) first.
This will only work on modules that import socket directly into the namespace;
most of the Python Standard Library falls into this category.
"""
if _defaultproxy != None:
module.socket.socket = socksocket
else:
raise GeneralProxyError((4, "no proxy specified"))
class socksocket(socket.socket):
"""socksocket([family[, type[, proto]]]) -> socket object
Open a SOCKS enabled socket. The parameters are the same as
those of the standard socket init. In order for SOCKS to work,
you must specify family=AF_INET, type=SOCK_STREAM and proto=0.
"""
def __init__(self, family=socket.AF_INET, type=socket.SOCK_STREAM, proto=0, _sock=None):
_orgsocket.__init__(self, family, type, proto, _sock)
if _defaultproxy != None:
self.__proxy = _defaultproxy
else:
self.__proxy = (None, None, None, None, None, None)
self.__proxysockname = None
self.__proxypeername = None
def __recvall(self, count):
"""__recvall(count) -> data
Receive EXACTLY the number of bytes requested from the socket.
Blocks until the required number of bytes have been received.
"""
data = self.recv(count)
while len(data) < count:
d = self.recv(count-len(data))
if not d: raise GeneralProxyError((0, "connection closed unexpectedly"))
data = data + d
return data
def setproxy(self, proxytype=None, addr=None, port=None, rdns=True, username=None, password=None):
"""setproxy(proxytype, addr[, port[, rdns[, username[, password]]]])
Sets the proxy to be used.
proxytype - The type of the proxy to be used. Three types
are supported: PROXY_TYPE_SOCKS4 (including socks4a),
PROXY_TYPE_SOCKS5 and PROXY_TYPE_HTTP
addr - The address of the server (IP or DNS).
port - The port of the server. Defaults to 1080 for SOCKS
servers and 8080 for HTTP proxy servers.
rdns - Should DNS queries be preformed on the remote side
(rather than the local side). The default is True.
Note: This has no effect with SOCKS4 servers.
username - Username to authenticate with to the server.
The default is no authentication.
password - Password to authenticate with to the server.
Only relevant when username is also provided.
"""
self.__proxy = (proxytype, addr, port, rdns, username, password)
def __negotiatesocks5(self, destaddr, destport):
"""__negotiatesocks5(self,destaddr,destport)
Negotiates a connection through a SOCKS5 server.
"""
# First we'll send the authentication packages we support.
if (self.__proxy[4]!=None) and (self.__proxy[5]!=None):
# The username/password details were supplied to the
# setproxy method so we support the USERNAME/PASSWORD
# authentication (in addition to the standard none).
self.sendall(struct.pack('BBBB', 0x05, 0x02, 0x00, 0x02))
else:
# No username/password were entered, therefore we
# only support connections with no authentication.
self.sendall(struct.pack('BBB', 0x05, 0x01, 0x00))
# We'll receive the server's response to determine which
# method was selected
chosenauth = self.__recvall(2)
if chosenauth[0:1] != chr(0x05).encode():
self.close()
raise GeneralProxyError((1, _generalerrors[1]))
# Check the chosen authentication method
if chosenauth[1:2] == chr(0x00).encode():
# No authentication is required
pass
elif chosenauth[1:2] == chr(0x02).encode():
# Okay, we need to perform a basic username/password
# authentication.
self.sendall(chr(0x01).encode() + chr(len(self.__proxy[4])) + self.__proxy[4] + chr(len(self.__proxy[5])) + self.__proxy[5])
authstat = self.__recvall(2)
if authstat[0:1] != chr(0x01).encode():
# Bad response
self.close()
raise GeneralProxyError((1, _generalerrors[1]))
if authstat[1:2] != chr(0x00).encode():
# Authentication failed
self.close()
raise Socks5AuthError((3, _socks5autherrors[3]))
# Authentication succeeded
else:
# Reaching here is always bad
self.close()
if chosenauth[1] == chr(0xFF).encode():
raise Socks5AuthError((2, _socks5autherrors[2]))
else:
raise GeneralProxyError((1, _generalerrors[1]))
# Now we can request the actual connection
req = struct.pack('BBB', 0x05, 0x01, 0x00)
# If the given destination address is an IP address, we'll
# use the IPv4 address request even if remote resolving was specified.
try:
ipaddr = socket.inet_aton(destaddr)
req = req + chr(0x01).encode() + ipaddr
except socket.error:
# Well it's not an IP number, so it's probably a DNS name.
if self.__proxy[3]:
# Resolve remotely
ipaddr = None
if type(destaddr) != type(b''): # python3
destaddr_bytes = destaddr.encode(encoding='idna')
else:
destaddr_bytes = destaddr
req = req + chr(0x03).encode() + chr(len(destaddr_bytes)).encode() + destaddr_bytes
else:
# Resolve locally
ipaddr = socket.inet_aton(socket.gethostbyname(destaddr))
req = req + chr(0x01).encode() + ipaddr
req = req + struct.pack(">H", destport)
self.sendall(req)
# Get the response
resp = self.__recvall(4)
if resp[0:1] != chr(0x05).encode():
self.close()
raise GeneralProxyError((1, _generalerrors[1]))
elif resp[1:2] != chr(0x00).encode():
# Connection failed
self.close()
if ord(resp[1:2])<=8:
raise Socks5Error((ord(resp[1:2]), _socks5errors[ord(resp[1:2])]))
else:
raise Socks5Error((9, _socks5errors[9]))
# Get the bound address/port
elif resp[3:4] == chr(0x01).encode():
boundaddr = self.__recvall(4)
elif resp[3:4] == chr(0x03).encode():
resp = resp + self.recv(1)
boundaddr = self.__recvall(ord(resp[4:5]))
else:
self.close()
raise GeneralProxyError((1,_generalerrors[1]))
boundport = struct.unpack(">H", self.__recvall(2))[0]
self.__proxysockname = (boundaddr, boundport)
if ipaddr != None:
self.__proxypeername = (socket.inet_ntoa(ipaddr), destport)
else:
self.__proxypeername = (destaddr, destport)
def getproxysockname(self):
"""getsockname() -> address info
Returns the bound IP address and port number at the proxy.
"""
return self.__proxysockname
def getproxypeername(self):
"""getproxypeername() -> address info
Returns the IP and port number of the proxy.
"""
return _orgsocket.getpeername(self)
def getpeername(self):
"""getpeername() -> address info
Returns the IP address and port number of the destination
machine (note: getproxypeername returns the proxy)
"""
return self.__proxypeername
def __negotiatesocks4(self,destaddr,destport):
"""__negotiatesocks4(self,destaddr,destport)
Negotiates a connection through a SOCKS4 server.
"""
# Check if the destination address provided is an IP address
rmtrslv = False
try:
ipaddr = socket.inet_aton(destaddr)
except socket.error:
# It's a DNS name. Check where it should be resolved.
if self.__proxy[3]:
ipaddr = struct.pack("BBBB", 0x00, 0x00, 0x00, 0x01)
rmtrslv = True
else:
ipaddr = socket.inet_aton(socket.gethostbyname(destaddr))
# Construct the request packet
req = struct.pack(">BBH", 0x04, 0x01, destport) + ipaddr
# The username parameter is considered userid for SOCKS4
if self.__proxy[4] != None:
req = req + self.__proxy[4]
req = req + chr(0x00).encode()
# DNS name if remote resolving is required
# NOTE: This is actually an extension to the SOCKS4 protocol
# called SOCKS4A and may not be supported in all cases.
if rmtrslv:
req = req + destaddr + chr(0x00).encode()
self.sendall(req)
# Get the response from the server
resp = self.__recvall(8)
if resp[0:1] != chr(0x00).encode():
# Bad data
self.close()
raise GeneralProxyError((1,_generalerrors[1]))
if resp[1:2] != chr(0x5A).encode():
# Server returned an error
self.close()
if ord(resp[1:2]) in (91, 92, 93):
self.close()
raise Socks4Error((ord(resp[1:2]), _socks4errors[ord(resp[1:2]) - 90]))
else:
raise Socks4Error((94, _socks4errors[4]))
# Get the bound address/port
self.__proxysockname = (socket.inet_ntoa(resp[4:]), struct.unpack(">H", resp[2:4])[0])
if rmtrslv != None:
self.__proxypeername = (socket.inet_ntoa(ipaddr), destport)
else:
self.__proxypeername = (destaddr, destport)
def __negotiatehttp(self, destaddr, destport):
"""__negotiatehttp(self,destaddr,destport)
Negotiates a connection through an HTTP server.
"""
# If we need to resolve locally, we do this now
if not self.__proxy[3]:
addr = socket.gethostbyname(destaddr)
else:
addr = destaddr
self.sendall(("CONNECT " + addr + ":" + str(destport) + " HTTP/1.1\r\n" + "Host: " + destaddr + "\r\n\r\n").encode())
# We read the response until we get the string "\r\n\r\n"
resp = self.recv(1)
while resp.find("\r\n\r\n".encode()) == -1:
recv = self.recv(1)
if not recv:
raise GeneralProxyError((1, _generalerrors[1]))
resp = resp + recv
# We just need the first line to check if the connection
# was successful
statusline = resp.splitlines()[0].split(" ".encode(), 2)
if statusline[0] not in ("HTTP/1.0".encode(), "HTTP/1.1".encode()):
self.close()
raise GeneralProxyError((1, _generalerrors[1]))
try:
statuscode = int(statusline[1])
except ValueError:
self.close()
raise GeneralProxyError((1, _generalerrors[1]))
if statuscode != 200:
self.close()
raise HTTPError((statuscode, statusline[2]))
self.__proxysockname = ("0.0.0.0", 0)
self.__proxypeername = (addr, destport)
def connect(self, destpair):
"""connect(self, despair)
Connects to the specified destination through a proxy.
destpar - A tuple of the IP/DNS address and the port number.
(identical to socket's connect).
To select the proxy server use setproxy().
"""
# Do a minimal input check first
if (not type(destpair) in (list,tuple)) or (len(destpair) < 2) or (type(destpair[0]) != type('')) or (type(destpair[1]) != int):
raise GeneralProxyError((5, _generalerrors[5]))
if self.__proxy[0] == PROXY_TYPE_SOCKS5:
if self.__proxy[2] != None:
portnum = int(self.__proxy[2])
else:
portnum = 1080
_orgsocket.connect(self, (self.__proxy[1], portnum))
self.__negotiatesocks5(destpair[0], destpair[1])
elif self.__proxy[0] == PROXY_TYPE_SOCKS4:
if self.__proxy[2] != None:
portnum = self.__proxy[2]
else:
portnum = 1080
_orgsocket.connect(self,(self.__proxy[1], portnum))
self.__negotiatesocks4(destpair[0], destpair[1])
elif self.__proxy[0] == PROXY_TYPE_HTTP:
if self.__proxy[2] != None:
portnum = self.__proxy[2]
else:
portnum = 8080
_orgsocket.connect(self,(self.__proxy[1], portnum))
self.__negotiatehttp(destpair[0], destpair[1])
elif self.__proxy[0] == None:
_orgsocket.connect(self, (destpair[0], destpair[1]))
else:
raise GeneralProxyError((4, _generalerrors[4]))

View File

@ -0,0 +1,914 @@
# -*- mode: python; indent-tabs-mode: nil; py-indent-offset: 4; coding: utf-8 -*-
import argparse
import contextlib
import inspect
import json
import logging
import os
import re
import select
import shutil
import socket
import sys
import time
import traceback
import unittest
from ctypes import *
from random import Random
import functools
random = Random()
try:
import coloredlogs
if 'COLOREDLOGS_LEVEL_STYLES' not in os.environ:
os.environ['COLOREDLOGS_LEVEL_STYLES'] = 'spam=22;debug=28;verbose=34;notice=220;warning=202;success=118,bold;error=124;critical=background=red'
# https://pypi.org/project/coloredlogs/
except ImportError as e:
coloredlogs = False
try:
import stem
except ImportError as e:
stem = False
try:
import nmap
except ImportError as e:
nmap = False
import wrapper
from wrapper.toxcore_enums_and_consts import TOX_CONNECTION, TOX_USER_STATUS
from wrapper_tests.support_http import bAreWeConnected
from wrapper_tests.support_onions import (is_valid_fingerprint,
lIntroductionPoints,
oGetStemController,
sMapaddressResolv, sTorResolve)
try:
from user_data.settings import get_user_config_path
except ImportError:
get_user_config_path = None
# LOG=util.log
global LOG
LOG = logging.getLogger()
def LOG_ERROR(l): print('ERRORc: '+l)
def LOG_WARN(l): print('WARNc: ' +l)
def LOG_INFO(l): print('INFOc: ' +l)
def LOG_DEBUG(l): print('DEBUGc: '+l)
def LOG_TRACE(l): pass # print('TRACE+ '+l)
try:
from trepan.api import debug
from trepan.interfaces import server as Mserver
except:
# print('trepan3 TCP server NOT available.')
pass
else:
# print('trepan3 TCP server available.')
def trepan_handler(num=None, f=None):
connection_opts={'IO': 'TCP', 'PORT': 6666}
intf = Mserver.ServerInterface(connection_opts=connection_opts)
dbg_opts = { 'interface': intf }
print(f'Starting TCP server listening on port 6666.')
debug(dbg_opts=dbg_opts)
return
# self._audio_thread.isAlive
iTHREAD_TIMEOUT = 1
iTHREAD_SLEEP = 1
iTHREAD_JOINS = 8
iNODES = 6
lToxSamplerates = [8000, 12000, 16000, 24000, 48000]
lToxSampleratesK = [8, 12, 16, 24, 48]
lBOOLEANS = [
'local_discovery_enabled',
'udp_enabled',
'ipv6_enabled',
'trace_enabled',
'compact_mode',
'allow_inline',
'notifications',
'sound_notifications',
'calls_sound',
'hole_punching_enabled',
'dht_announcements_enabled',
'save_history',
'download_nodes_list'
'core_logging',
]
sDIR = os.environ.get('TMPDIR', '/tmp')
sTOX_VERSION = "1000002018"
bHAVE_NMAP = shutil.which('nmap')
bHAVE_JQ = shutil.which('jq')
bHAVE_BASH = shutil.which('bash')
bHAVE_TORR = shutil.which('tor-resolve')
lDEAD_BS = [
# Failed to resolve "tox3.plastiras.org"
"tox3.plastiras.org",
'tox.kolka.tech',
# IPs that do not reverse resolve
'49.12.229.145',
"46.101.197.175",
'114.35.245.150',
'172.93.52.70',
'195.123.208.139',
'205.185.115.131',
# IPs that do not rreverse resolve
'yggnode.cf', '188.225.9.167',
'85-143-221-42.simplecloud.ru', '85.143.221.42',
# IPs that do not ping
'104.244.74.69', 'tox.plastiras.org',
'195.123.208.139',
'gt.sot-te.ch', '32.226.5.82',
# suspicious IPs
'tox.abilinski.com', '172.103.164.250', '172.103.164.250.tpia.cipherkey.com',
]
def assert_main_thread():
from PyQt5 import QtCore, QtWidgets
from qtpy.QtWidgets import QApplication
# this "instance" method is very useful!
app_thread = QtWidgets.QApplication.instance().thread()
curr_thread = QtCore.QThread.currentThread()
if app_thread != curr_thread:
raise RuntimeError('attempt to call MainWindow.append_message from non-app thread')
@contextlib.contextmanager
def ignoreStdout():
devnull = os.open(os.devnull, os.O_WRONLY)
old_stdout = os.dup(1)
sys.stdout.flush()
os.dup2(devnull, 1)
os.close(devnull)
try:
yield
finally:
os.dup2(old_stdout, 1)
os.close(old_stdout)
@contextlib.contextmanager
def ignoreStderr():
devnull = os.open(os.devnull, os.O_WRONLY)
old_stderr = os.dup(2)
sys.stderr.flush()
os.dup2(devnull, 2)
os.close(devnull)
try:
yield
finally:
os.dup2(old_stderr, 2)
os.close(old_stderr)
def clean_booleans(oArgs):
for key in lBOOLEANS:
if not hasattr(oArgs, key): continue
val = getattr(oArgs, key)
if type(val) == bool: continue
if val in ['False', 'false', '0']:
setattr(oArgs, key, False)
else:
setattr(oArgs, key, True)
def on_log(iTox, level, filename, line, func, message, *data):
# LOG.debug(repr((level, filename, line, func, message,)))
tox_log_cb(level, filename, line, func, message)
def tox_log_cb(level, filename, line, func, message, *args):
"""
* @param level The severity of the log message.
* @param filename The source file from which the message originated.
* @param line The source line from which the message originated.
* @param func The function from which the message originated.
* @param message The log message.
* @param user_data The user data pointer passed to tox_new in options.
"""
if type(func) == bytes:
func = str(func, 'utf-8')
message = str(message, 'UTF-8')
filename = str(filename, 'UTF-8')
if filename == 'network.c':
if line == 660: return
# root WARNING 3network.c#944:b'send_packet'attempted to send message with network family 10 (probably IPv6) on IPv4 socket
if line == 944: return
i = message.find('07 = GET_NODES')
if i > 0:
return
if filename == 'TCP_common.c': return
i = message.find(' | ')
if i > 0:
message = message[:i]
# message = filename +'#' +str(line) +':'+func +' '+message
name = 'core'
# old level is meaningless
level = 10 # LOG.level
# LOG._log(LOG.level, f"{level}: {message}", list())
i = message.find('(0: OK)')
if i > 0:
level = 10 # LOG.debug
else:
i = message.find('(1: ')
if i > 0:
level = 30 # LOG.warn
else:
level = 20 # LOG.info
o = LOG.makeRecord(filename, level, func, line, message, list(), None)
# LOG.handle(o)
LOG_TRACE(f"{level}: {func}{line} {message}")
return
elif level == 1:
LOG.critical(f"{level}: {message}")
elif level == 2:
LOG.error(f"{level}: {message}")
elif level == 3:
LOG.warn(f"{level}: {message}")
elif level == 4:
LOG.info(f"{level}: {message}")
elif level == 5:
LOG.debug(f"{level}: {message}")
else:
LOG_TRACE(f"{level}: {message}")
def vAddLoggerCallback(tox_options, callback=None):
if callback is None:
wrapper.tox.Tox.libtoxcore.tox_options_set_log_callback(
tox_options._options_pointer,
POINTER(None)())
tox_options.self_logger_cb = None
return
c_callback = CFUNCTYPE(None, c_void_p, c_int, c_char_p, c_int, c_char_p, c_char_p, c_void_p)
tox_options.self_logger_cb = c_callback(callback)
wrapper.tox.Tox.libtoxcore.tox_options_set_log_callback(
tox_options._options_pointer,
tox_options.self_logger_cb)
def get_video_indexes():
# Linux
return [str(l[5:]) for l in os.listdir('/dev/') if l.startswith('video')]
def get_audio():
with ignoreStderr():
import pyaudio
oPyA = pyaudio.PyAudio()
input_devices = output_devices = 0
for i in range(oPyA.get_device_count()):
device = oPyA.get_device_info_by_index(i)
if device["maxInputChannels"]:
input_devices += 1
if device["maxOutputChannels"]:
output_devices += 1
# {'index': 21, 'structVersion': 2, 'name': 'default', 'hostApi': 0, 'maxInputChannels': 64, 'maxOutputChannels': 64, 'defaultLowInputLatency': 0.008707482993197279, 'defaultLowOutputLatency': 0.008707482993197279, 'defaultHighInputLatency': 0.034829931972789115, 'defaultHighOutputLatency': 0.034829931972789115, 'defaultSampleRate': 44100.0}
audio = {'input': oPyA.get_default_input_device_info()['index'] if input_devices else -1,
'output': oPyA.get_default_output_device_info()['index'] if output_devices else -1,
'enabled': input_devices and output_devices}
return audio
def oMainArgparser(_=None, iMode=0):
# 'Mode: 0=chat 1=chat+audio 2=chat+audio+video default: 0'
if not os.path.exists('/proc/sys/net/ipv6'):
bIpV6 = 'False'
else:
bIpV6 = 'True'
lIpV6Choices=[bIpV6, 'False']
sNodesJson = os.path.join(os.environ['HOME'], '.config', 'tox', 'DHTnodes.json')
if not os.path.exists(sNodesJson): sNodesJson = ''
logfile = os.path.join(os.environ.get('TMPDIR', '/tmp'), 'toxygen.log')
if not os.path.exists(sNodesJson): logfile = ''
parser = argparse.ArgumentParser(add_help=True)
parser.add_argument('--proxy_host', '--proxy-host', type=str,
# oddball - we want to use '' as a setting
default='0.0.0.0',
help='proxy host')
parser.add_argument('--proxy_port', '--proxy-port', default=0, type=int,
help='proxy port')
parser.add_argument('--proxy_type', '--proxy-type', default=0, type=int,
choices=[0,1,2],
help='proxy type 1=http, 2=socks')
parser.add_argument('--tcp_port', '--tcp-port', default=0, type=int,
help='tcp port')
parser.add_argument('--udp_enabled', type=str, default='True',
choices=['True', 'False'],
help='En/Disable udp')
parser.add_argument('--ipv6_enabled', type=str, default=bIpV6,
choices=lIpV6Choices,
help=f"En/Disable ipv6 - default {bIpV6}")
parser.add_argument('--trace_enabled',type=str,
default='True' if os.environ.get('DEBUG') else 'False',
choices=['True','False'],
help='Debugging from toxcore logger_trace or env DEBUG=1')
parser.add_argument('--download_nodes_list', type=str, default='False',
choices=['True', 'False'],
help='Download nodes list')
parser.add_argument('--nodes_json', type=str,
default=sNodesJson)
parser.add_argument('--network', type=str,
choices=['main', 'local'],
default='main')
parser.add_argument('--download_nodes_url', type=str,
default='https://nodes.tox.chat/json')
parser.add_argument('--logfile', default=logfile,
help='Filename for logging - start with + for stdout too')
parser.add_argument('--loglevel', default=logging.INFO, type=int,
# choices=[logging.info,logging.trace,logging.debug,logging.error]
help='Threshold for logging (lower is more) default: 20')
parser.add_argument('--mode', type=int, default=iMode,
choices=[0,1,2],
help='Mode: 0=chat 1=chat+audio 2=chat+audio+video default: 0')
parser.add_argument('--hole_punching_enabled',type=str,
default='False', choices=['True','False'],
help='En/Enable hole punching')
parser.add_argument('--dht_announcements_enabled',type=str,
default='True', choices=['True','False'],
help='En/Disable DHT announcements')
return parser
def vSetupLogging(oArgs):
global LOG
logging._defaultFormatter = logging.Formatter(datefmt='%m-%d %H:%M:%S')
logging._defaultFormatter.default_time_format = '%m-%d %H:%M:%S'
logging._defaultFormatter.default_msec_format = ''
add = None
kwargs = dict(level=oArgs.loglevel,
format='%(levelname)-8s %(message)s')
if oArgs.logfile:
add = oArgs.logfile.startswith('+')
sub = oArgs.logfile.startswith('-')
if add or sub:
oArgs.logfile = oArgs.logfile[1:]
kwargs['filename'] = oArgs.logfile
if coloredlogs:
# https://pypi.org/project/coloredlogs/
aKw = dict(level=oArgs.loglevel,
logger=LOG,
stream=sys.stdout,
fmt='%(name)s %(levelname)s %(message)s'
)
coloredlogs.install(**aKw)
if oArgs.logfile:
oHandler = logging.FileHandler(oArgs.logfile)
LOG.addHandler(oHandler)
else:
logging.basicConfig(**kwargs)
if add:
oHandler = logging.StreamHandler(sys.stdout)
LOG.addHandler(oHandler)
LOG.info(f"Setting loglevel to {oArgs.loglevel!s}")
def setup_logging(oArgs):
global LOG
if coloredlogs:
aKw = dict(level=oArgs.loglevel,
logger=LOG,
fmt='%(name)s %(levelname)s %(message)s')
if oArgs.logfile:
oFd = open(oArgs.logfile, 'wt')
setattr(oArgs, 'log_oFd', oFd)
aKw['stream'] = oFd
coloredlogs.install(**aKw)
if oArgs.logfile:
oHandler = logging.StreamHandler(stream=sys.stdout)
LOG.addHandler(oHandler)
else:
aKw = dict(level=oArgs.loglevel,
format='%(name)s %(levelname)-4s %(message)s')
if oArgs.logfile:
aKw['filename'] = oArgs.logfile
logging.basicConfig(**aKw)
logging._defaultFormatter = logging.Formatter(datefmt='%m-%d %H:%M:%S')
logging._defaultFormatter.default_time_format = '%m-%d %H:%M:%S'
logging._defaultFormatter.default_msec_format = ''
LOG.setLevel(oArgs.loglevel)
# LOG.trace = lambda l: LOG.log(0, repr(l))
LOG.info(f"Setting loglevel to {oArgs.loglevel!s}")
def signal_handler(num, f):
from trepan.api import debug
from trepan.interfaces import server as Mserver
connection_opts={'IO': 'TCP', 'PORT': 6666}
intf = Mserver.ServerInterface(connection_opts=connection_opts)
dbg_opts = {'interface': intf}
LOG.info('Starting TCP server listening on port 6666.')
debug(dbg_opts=dbg_opts)
return
def merge_args_into_settings(args, settings):
if args:
if not hasattr(args, 'audio'):
LOG.warn('No audio ' +repr(args))
settings['audio'] = getattr(args, 'audio')
if not hasattr(args, 'video'):
LOG.warn('No video ' +repr(args))
settings['video'] = getattr(args, 'video')
for key in settings.keys():
# proxy_type proxy_port proxy_host
not_key = 'not_' +key
if hasattr(args, key):
val = getattr(args, key)
if type(val) == bytes:
# proxy_host - ascii?
# filenames - ascii?
val = str(val, 'UTF-8')
settings[key] = val
elif hasattr(args, not_key):
val = not getattr(args, not_key)
settings[key] = val
clean_settings(settings)
return
def clean_settings(self):
# failsafe to ensure C tox is bytes and Py settings is str
# overrides
self['mirror_mode'] = False
# REQUIRED!!
if not os.path.exists('/proc/sys/net/ipv6'):
LOG.warn('Disabling IPV6 because /proc/sys/net/ipv6 does not exist')
self['ipv6_enabled'] = False
if 'proxy_type' in self and self['proxy_type'] == 0:
self['proxy_host'] = ''
self['proxy_port'] = 0
if 'proxy_type' in self and self['proxy_type'] != 0 and \
'proxy_host' in self and self['proxy_host'] != '' and \
'proxy_port' in self and self['proxy_port'] != 0:
if 'udp_enabled' in self and self['udp_enabled']:
# We don't currently support UDP over proxy.
LOG.info("UDP enabled and proxy set: disabling UDP")
self['udp_enabled'] = False
if 'local_discovery_enabled' in self and self['local_discovery_enabled']:
LOG.info("local_discovery_enabled enabled and proxy set: disabling local_discovery_enabled")
self['local_discovery_enabled'] = False
if 'dht_announcements_enabled' in self and self['dht_announcements_enabled']:
LOG.info("dht_announcements_enabled enabled and proxy set: disabling dht_announcements_enabled")
self['dht_announcements_enabled'] = False
if 'auto_accept_path' in self and \
type(self['auto_accept_path']) == bytes:
self['auto_accept_path'] = str(self['auto_accept_path'], 'UTF-8')
LOG.debug("Cleaned settings")
def lSdSamplerates(iDev):
try:
import sounddevice as sd
except ImportError:
return []
samplerates = (32000, 44100, 48000, 96000, )
device = iDev
supported_samplerates = []
for fs in samplerates:
try:
sd.check_output_settings(device=device, samplerate=fs)
except Exception as e:
# LOG.debug(f"Sample rate not supported {fs}" +' '+str(e))
pass
else:
supported_samplerates.append(fs)
return supported_samplerates
def _get_nodes_path(oArgs=None):
if oArgs and oArgs.nodes_json and os.path.isfile(oArgs.nodes_json):
LOG.debug("_get_nodes_path: " +oArgs.nodes_json)
default = oArgs.nodes_json
elif get_user_config_path:
default = os.path.join(get_user_config_path(), 'toxygen_nodes.json')
else:
# Windwoes
default = os.path.join(os.getenv('HOME'), '.config', 'tox', 'toxygen_nodes.json')
LOG.debug("_get_nodes_path: " +default)
return default
DEFAULT_NODES_COUNT = 8
global aNODES
aNODES = {}
# @functools.lru_cache(maxsize=12) TypeError: unhashable type: 'Namespace'
def generate_nodes(oArgs=None,
nodes_count=DEFAULT_NODES_COUNT,
ipv='ipv4',
udp_not_tcp=True):
global aNODES
sKey = ipv
sKey += ',0' if udp_not_tcp else ',1'
if sKey in aNODES and aNODES[sKey]:
return aNODES[sKey]
sFile = _get_nodes_path(oArgs=oArgs)
assert os.path.exists(sFile), sFile
lNodes = generate_nodes_from_file(sFile,
nodes_count=nodes_count,
ipv=ipv, udp_not_tcp=udp_not_tcp)
assert lNodes
aNODES[sKey] = lNodes
return aNODES[sKey]
aNODES_CACHE = {}
def generate_nodes_from_file(sFile,
nodes_count=DEFAULT_NODES_COUNT,
ipv='ipv4',
udp_not_tcp=True,
):
"""https://github.com/TokTok/c-toxcore/issues/469
I had a conversation with @irungentoo on IRC about whether we really need to call tox_bootstrap() when having UDP disabled and why. The answer is yes, because in addition to TCP relays (tox_add_tcp_relay()), toxcore also needs to know addresses of UDP onion nodes in order to work correctly. The DHT, however, is not used when UDP is disabled. tox_bootstrap() function resolves the address passed to it as argument and calls onion_add_bs_node_path() and DHT_bootstrap() functions. Although calling DHT_bootstrap() is not really necessary as DHT is not used, we still need to resolve the address of the DHT node in order to populate the onion routes with onion_add_bs_node_path() call.
"""
global aNODES_CACHE
key = ipv
key += ',0' if udp_not_tcp else ',1'
if key in aNODES_CACHE:
sorted_nodes = aNODES_CACHE[key]
else:
if not os.path.exists(sFile):
LOG.error("generate_nodes_from_file file not found " +sFile)
return []
try:
with open(sFile, 'rt') as fl:
json_nodes = json.loads(fl.read())['nodes']
except Exception as e:
LOG.error(f"generate_nodes_from_file error {sFile}\n{e}")
return []
else:
LOG.debug("generate_nodes_from_file " +sFile)
if udp_not_tcp:
nodes = [(node[ipv], node['port'], node['public_key'],) for
node in json_nodes if node[ipv] != 'NONE' \
and node["status_udp"] in [True, "true"]
]
else:
nodes = []
elts = [(node[ipv], node['tcp_ports'], node['public_key'],) \
for node in json_nodes if node[ipv] != 'NONE' \
and node["status_tcp"] in [True, "true"]
]
for (ipv, ports, public_key,) in elts:
for port in ports:
nodes += [(ipv, port, public_key)]
if not nodes:
LOG.warn(f'empty generate_nodes from {sFile} {json_nodes!r}')
return []
sorted_nodes = nodes
aNODES_CACHE[key] = sorted_nodes
random.shuffle(sorted_nodes)
if nodes_count is not None and len(sorted_nodes) > nodes_count:
sorted_nodes = sorted_nodes[-nodes_count:]
LOG.debug(f"generate_nodes_from_file {sFile} len={len(sorted_nodes)}")
return sorted_nodes
def tox_bootstrapd_port():
port = 33446
sFile = '/etc/tox-bootstrapd.conf'
if os.path.exists(sFile):
with open(sFile, 'rt') as oFd:
for line in oFd.readlines():
if line.startswith('port = '):
port = int(line[7:])
return port
def bootstrap_local(elts, lToxes, oArgs=None):
if os.path.exists('/run/tox-bootstrapd/tox-bootstrapd.pid'):
LOG.debug('/run/tox-bootstrapd/tox-bootstrapd.pid')
iRet = True
else:
iRet = os.system("netstat -nle4|grep -q :33")
if iRet > 0:
LOG.warn(f'bootstraping local No local DHT running')
LOG.info(f'bootstraping local')
return bootstrap_udp(elts, lToxes, oArgs)
def lDNSClean(l):
global lDEAD_BS
# list(set(l).difference(set(lDEAD_BS)))
return [elt for elt in l if elt not in lDEAD_BS]
def lExitExcluder(oArgs, iPort=9051):
"""
https://raw.githubusercontent.com/nusenu/noContactInfo_Exit_Excluder/main/exclude_noContactInfo_Exits.py
"""
if not stem:
LOG.warn('please install the stem Python package')
return ''
LOG.debug('lExcludeExitNodes')
try:
controller = oGetStemController(log_level=10)
# generator
relays = controller.get_server_descriptors()
except Exception as e:
LOG.error(f'Failed to get relay descriptors {e}')
return None
if controller.is_set('ExcludeExitNodes'):
LOG.info('ExcludeExitNodes is in use already.')
return None
exit_excludelist=[]
LOG.debug("Excluded exit relays:")
for relay in relays:
if relay.exit_policy.is_exiting_allowed() and not relay.contact:
if is_valid_fingerprint(relay.fingerprint):
exit_excludelist.append(relay.fingerprint)
LOG.debug("https://metrics.torproject.org/rs.html#details/%s" % relay.fingerprint)
else:
LOG.warn('Invalid Fingerprint: %s' % relay.fingerprint)
try:
controller.set_conf('ExcludeExitNodes', exit_excludelist)
LOG.info('Excluded a total of %s exit relays without ContactInfo from the exit position.' % len(exit_excludelist))
except Exception as e:
LOG.exception('ExcludeExitNodes ' +str(e))
return exit_excludelist
aHOSTS = {}
@functools.lru_cache(maxsize=20)
def sDNSLookup(host):
global aHOSTS
ipv = 0
if host in lDEAD_BS:
# LOG.warn(f"address skipped because in lDEAD_BS {host}")
return ''
if host in aHOSTS:
return aHOSTS[host]
try:
s = host.replace('.','')
int(s)
ipv = 4
except:
try:
s = host.replace(':','')
int(s)
ipv = 6
except: pass
if ipv > 0:
# LOG.debug(f"v={ipv} IP address {host}")
return host
LOG.debug(f"sDNSLookup {host}")
ip = ''
if host.endswith('.tox') or host.endswith('.onion'):
if False and stem:
ip = sMapaddressResolv(host)
if ip: return ip
ip = sTorResolve(host)
if ip: return ip
if not bHAVE_TORR:
LOG.warn(f"onion address skipped because no tor-resolve {host}")
return ''
try:
sout = f"/tmp/TR{os.getpid()}.log"
i = os.system(f"tor-resolve -4 {host} > {sout}")
if not i:
LOG.warn(f"onion address skipped because tor-resolve on {host}")
return ''
ip = open(sout, 'rt').read()
if ip.endswith('failed.'):
LOG.warn(f"onion address skipped because tor-resolve failed on {host}")
return ''
LOG.debug(f"onion address tor-resolve {ip} on {host}")
return ip
except:
pass
else:
try:
ip = socket.gethostbyname(host)
LOG.debug(f"host={host} gethostbyname IP address {ip}")
if ip:
aHOSTS[host] = ip
return ip
# drop through
except:
# drop through
pass
if ip == '':
try:
sout = f"/tmp/TR{os.getpid()}.log"
i = os.system(f"dig {host} +timeout=15|grep ^{host}|sed -e 's/.* //'> {sout}")
if not i:
LOG.warn(f"address skipped because dig failed on {host}")
return ''
ip = open(sout, 'rt').read().strip()
LOG.debug(f"address dig {ip} on {host}")
aHOSTS[host] = ip
return ip
except:
ip = host
LOG.debug(f'sDNSLookup {host} -> {ip}')
if ip and ip != host:
aHOSTS[host] = ip
return ip
def bootstrap_udp(lelts, lToxes, oArgs=None):
lelts = lDNSClean(lelts)
socket.setdefaulttimeout(15.0)
for oTox in lToxes:
random.shuffle(lelts)
if hasattr(oTox, 'oArgs'):
oArgs = oTox.oArgs
if hasattr(oArgs, 'contents') and oArgs.contents.proxy_type != 0:
lelts = lelts[:1]
# LOG.debug(f'bootstrap_udp DHT bootstraping {oTox.name} {len(lelts)}')
for largs in lelts:
assert len(largs) == 3
host, port, key = largs
assert host; assert port; assert key
if host in lDEAD_BS: continue
ip = sDNSLookup(host)
if not ip:
LOG.warn(f'bootstrap_udp to host={host} port={port} did not resolve ip={ip}')
continue
if type(port) == str:
port = int(port)
try:
assert len(key) == 64, key
# NOT ip
oRet = oTox.bootstrap(host,
port,
key)
except Exception as e:
if oArgs is None or (
hasattr(oArgs, 'contents') and oArgs.contents.proxy_type == 0):
pass
# LOG.error(f'bootstrap_udp failed to host={host} port={port} {e}')
continue
if not oRet:
LOG.warn(f'bootstrap_udp failed to {host} : {oRet}')
elif oTox.self_get_connection_status() != TOX_CONNECTION['NONE']:
LOG.info(f'bootstrap_udp to {host} connected')
break
else:
# LOG.debug(f'bootstrap_udp to {host} not connected')
pass
def bootstrap_tcp(lelts, lToxes, oArgs=None):
lelts = lDNSClean(lelts)
for oTox in lToxes:
if hasattr(oTox, 'oArgs'): oArgs = oTox.oArgs
random.shuffle(lelts)
# LOG.debug(f'bootstrap_tcp bootstapping {oTox.name} {len(lelts)}')
for (host, port, key,) in lelts:
assert host; assert port;assert key
if host in lDEAD_BS: continue
ip = sDNSLookup(host)
if not ip:
LOG.warn(f'bootstrap_tcp to {host} did not resolve ip={ip}')
# continue
ip = host
if host.endswith('.onion') and stem:
l = lIntroductionPoints(host)
if not l:
LOG.warn(f'bootstrap_tcp to {host} has no introduction points')
continue
if type(port) == str:
port = int(port)
try:
assert len(key) == 64, key
oRet = oTox.add_tcp_relay(ip,
port,
key)
except Exception as e:
LOG.error(f'bootstrap_tcp to {host} : ' +str(e))
continue
if not oRet:
LOG.warn(f'bootstrap_tcp failed to {host} : {oRet}')
elif oTox.mycon_time == 1:
LOG.info(f'bootstrap_tcp to {host} not yet connected last=1')
elif oTox.mycon_status is False:
LOG.info(f'bootstrap_tcp to {host} not True' \
+f" last={int(oTox.mycon_time)}" )
elif oTox.self_get_connection_status() != TOX_CONNECTION['NONE']:
LOG.info(f'bootstrap_tcp to {host} connected' \
+f" last={int(oTox.mycon_time)}" )
break
else:
LOG.debug(f'bootstrap_tcp to {host} but not connected' \
+f" last={int(oTox.mycon_time)}" )
pass
def iNmapInfoNmap(sProt, sHost, sPort, key=None, environ=None, cmd=''):
if sHost in ['-', 'NONE']: return 0
if not nmap: return 0
nmps = nmap.PortScanner
if sProt in ['socks', 'socks5', 'tcp4']:
prot = 'tcp'
cmd = f" -Pn -n -sT -p T:{sPort}"
else:
prot = 'udp'
cmd = f" -Pn -n -sU -p U:{sPort}"
LOG.debug(f"iNmapInfoNmap cmd={cmd}")
sys.stdout.flush()
o = nmps().scan(hosts=sHost, arguments=cmd)
aScan = o['scan']
ip = list(aScan.keys())[0]
state = aScan[ip][prot][sPort]['state']
LOG.info(f"iNmapInfoNmap: to {sHost} {state}")
return 0
def iNmapInfo(sProt, sHost, sPort, key=None, environ=None, cmd='nmap'):
if sHost in ['-', 'NONE']: return 0
sFile = os.path.join("/tmp", f"{sHost}.{os.getpid()}.nmap")
if sProt in ['socks', 'socks5', 'tcp4']:
cmd += f" -Pn -n -sT -p T:{sPort} {sHost} | grep /tcp "
else:
cmd += f" -Pn -n -sU -p U:{sPort} {sHost} | grep /udp "
LOG.debug(f"iNmapInfo cmd={cmd}")
sys.stdout.flush()
iRet = os.system('sudo ' +cmd +f" >{sFile} 2>&1 ")
LOG.debug(f"iNmapInfo cmd={cmd} iRet={iRet}")
if iRet != 0:
return iRet
assert os.path.exists(sFile), sFile
with open(sFile, 'rt') as oFd:
l = oFd.readlines()
assert len(l)
l = [line for line in l if line and not line.startswith('WARNING:')]
s = '\n'.join([s.strip() for s in l])
LOG.info(f"iNmapInfo: to {sHost}\n{s}")
return 0
def bootstrap_iNmapInfo(lElts, oArgs, protocol="tcp4", bIS_LOCAL=False, iNODES=iNODES, cmd='nmap'):
if not bIS_LOCAL and not bAreWeConnected():
LOG.warn(f"bootstrap_iNmapInfo not local and NOT CONNECTED")
return True
if os.environ['USER'] != 'root':
LOG.warn(f"bootstrap_iNmapInfo not ROOT")
return True
lRetval = []
for elts in lElts[:iNODES]:
host, port, key = elts
ip = sDNSLookup(host)
if not ip:
LOG.info('bootstrap_iNmapInfo to {host} did not resolve ip={ip}')
continue
if type(port) == str:
port = int(port)
iRet = -1
try:
if not nmap:
iRet = iNmapInfo(protocol, ip, port, key, cmd=cmd)
else:
iRet = iNmapInfoNmap(protocol, ip, port, key)
if iRet != 0:
LOG.warn('iNmapInfo to ' +repr(host) +' retval=' +str(iRet))
lRetval += [False]
else:
LOG.debug('iNmapInfo to ' +repr(host) +' retval=' +str(iRet))
lRetval += [True]
except Exception as e:
LOG.exception('iNmapInfo to {host} : ' +str(e)
)
lRetval += [False]
return any(lRetval)
def caseFactory(cases):
"""We want the tests run in order."""
if len(cases) > 1:
ordered_cases = sorted(cases, key=lambda f: inspect.findsource(f)[1])
else:
ordered_cases = cases
return ordered_cases
def suiteFactory(*testcases):
"""We want the tests run in order."""
linen = lambda f: getattr(tc, f).__code__.co_firstlineno
lncmp = lambda a, b: linen(a) - linen(b)
test_suite = unittest.TestSuite()
for tc in testcases:
test_suite.addTest(unittest.makeSuite(tc, sortUsing=lncmp))
return test_suite

936
toxygen/tests/test_gdb.py Normal file
View File

@ -0,0 +1,936 @@
# Verify that gdb can pretty-print the various PyObject* types
#
# The code for testing gdb was adapted from similar work in Unladen Swallow's
# Lib/test/test_jit_gdb.py
import locale
import os
import re
import subprocess
import sys
import sysconfig
import textwrap
import unittest
# Is this Python configured to support threads?
try:
import _thread
except ImportError:
_thread = None
from test import support
from test.support import run_unittest, findfile, python_is_optimized
def get_gdb_version():
try:
proc = subprocess.Popen(["gdb", "-nx", "--version"],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
universal_newlines=True)
with proc:
version = proc.communicate()[0]
except OSError:
# This is what "no gdb" looks like. There may, however, be other
# errors that manifest this way too.
raise unittest.SkipTest("Couldn't find gdb on the path")
# Regex to parse:
# 'GNU gdb (GDB; SUSE Linux Enterprise 12) 7.7\n' -> 7.7
# 'GNU gdb (GDB) Fedora 7.9.1-17.fc22\n' -> 7.9
# 'GNU gdb 6.1.1 [FreeBSD]\n' -> 6.1
# 'GNU gdb (GDB) Fedora (7.5.1-37.fc18)\n' -> 7.5
match = re.search(r"^GNU gdb.*?\b(\d+)\.(\d+)", version)
if match is None:
raise Exception("unable to parse GDB version: %r" % version)
return (version, int(match.group(1)), int(match.group(2)))
gdb_version, gdb_major_version, gdb_minor_version = get_gdb_version()
if gdb_major_version < 7:
raise unittest.SkipTest("gdb versions before 7.0 didn't support python "
"embedding. Saw %s.%s:\n%s"
% (gdb_major_version, gdb_minor_version,
gdb_version))
if not sysconfig.is_python_build():
raise unittest.SkipTest("test_gdb only works on source builds at the moment.")
# Location of custom hooks file in a repository checkout.
checkout_hook_path = os.path.join(os.path.dirname(sys.executable),
'python-gdb.py')
PYTHONHASHSEED = '123'
def run_gdb(*args, **env_vars):
"""Runs gdb in --batch mode with the additional arguments given by *args.
Returns its (stdout, stderr) decoded from utf-8 using the replace handler.
"""
if env_vars:
env = os.environ.copy()
env.update(env_vars)
else:
env = None
# -nx: Do not execute commands from any .gdbinit initialization files
# (issue #22188)
base_cmd = ('gdb', '--batch', '-nx')
if (gdb_major_version, gdb_minor_version) >= (7, 4):
base_cmd += ('-iex', 'add-auto-load-safe-path ' + checkout_hook_path)
proc = subprocess.Popen(base_cmd + args,
# Redirect stdin to prevent GDB from messing with
# the terminal settings
stdin=subprocess.PIPE,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
env=env)
with proc:
out, err = proc.communicate()
return out.decode('utf-8', 'replace'), err.decode('utf-8', 'replace')
# Verify that "gdb" was built with the embedded python support enabled:
gdbpy_version, _ = run_gdb("--eval-command=python import sys; print(sys.version_info)")
if not gdbpy_version:
raise unittest.SkipTest("gdb not built with embedded python support")
# Verify that "gdb" can load our custom hooks, as OS security settings may
# disallow this without a customized .gdbinit.
_, gdbpy_errors = run_gdb('--args', sys.executable)
if "auto-loading has been declined" in gdbpy_errors:
msg = "gdb security settings prevent use of custom hooks: "
raise unittest.SkipTest(msg + gdbpy_errors.rstrip())
def gdb_has_frame_select():
# Does this build of gdb have gdb.Frame.select ?
stdout, _ = run_gdb("--eval-command=python print(dir(gdb.Frame))")
m = re.match(r'.*\[(.*)\].*', stdout)
if not m:
raise unittest.SkipTest("Unable to parse output from gdb.Frame.select test")
gdb_frame_dir = m.group(1).split(', ')
return "'select'" in gdb_frame_dir
HAS_PYUP_PYDOWN = gdb_has_frame_select()
BREAKPOINT_FN='builtin_id'
@unittest.skipIf(support.PGO, "not useful for PGO")
class DebuggerTests(unittest.TestCase):
"""Test that the debugger can debug Python."""
def get_stack_trace(self, source=None, script=None,
breakpoint=BREAKPOINT_FN,
cmds_after_breakpoint=None,
import_site=False):
'''
Run 'python -c SOURCE' under gdb with a breakpoint.
Support injecting commands after the breakpoint is reached
Returns the stdout from gdb
cmds_after_breakpoint: if provided, a list of strings: gdb commands
'''
# We use "set breakpoint pending yes" to avoid blocking with a:
# Function "foo" not defined.
# Make breakpoint pending on future shared library load? (y or [n])
# error, which typically happens python is dynamically linked (the
# breakpoints of interest are to be found in the shared library)
# When this happens, we still get:
# Function "textiowrapper_write" not defined.
# emitted to stderr each time, alas.
# Initially I had "--eval-command=continue" here, but removed it to
# avoid repeated print breakpoints when traversing hierarchical data
# structures
# Generate a list of commands in gdb's language:
commands = ['set breakpoint pending yes',
'break %s' % breakpoint,
# The tests assume that the first frame of printed
# backtrace will not contain program counter,
# that is however not guaranteed by gdb
# therefore we need to use 'set print address off' to
# make sure the counter is not there. For example:
# #0 in PyObject_Print ...
# is assumed, but sometimes this can be e.g.
# #0 0x00003fffb7dd1798 in PyObject_Print ...
'set print address off',
'run']
# GDB as of 7.4 onwards can distinguish between the
# value of a variable at entry vs current value:
# http://sourceware.org/gdb/onlinedocs/gdb/Variables.html
# which leads to the selftests failing with errors like this:
# AssertionError: 'v@entry=()' != '()'
# Disable this:
if (gdb_major_version, gdb_minor_version) >= (7, 4):
commands += ['set print entry-values no']
if cmds_after_breakpoint:
commands += cmds_after_breakpoint
else:
commands += ['backtrace']
# print commands
# Use "commands" to generate the arguments with which to invoke "gdb":
args = ['--eval-command=%s' % cmd for cmd in commands]
args += ["--args",
sys.executable]
args.extend(subprocess._args_from_interpreter_flags())
if not import_site:
# -S suppresses the default 'import site'
args += ["-S"]
if source:
args += ["-c", source]
elif script:
args += [script]
# print args
# print (' '.join(args))
# Use "args" to invoke gdb, capturing stdout, stderr:
out, err = run_gdb(*args, PYTHONHASHSEED=PYTHONHASHSEED)
errlines = err.splitlines()
unexpected_errlines = []
# Ignore some benign messages on stderr.
ignore_patterns = (
'Function "%s" not defined.' % breakpoint,
'Do you need "set solib-search-path" or '
'"set sysroot"?',
# BFD: /usr/lib/debug/(...): unable to initialize decompress
# status for section .debug_aranges
'BFD: ',
# ignore all warnings
'warning: ',
)
for line in errlines:
if not line:
continue
if not line.startswith(ignore_patterns):
unexpected_errlines.append(line)
# Ensure no unexpected error messages:
self.assertEqual(unexpected_errlines, [])
return out
def get_gdb_repr(self, source,
cmds_after_breakpoint=None,
import_site=False):
# Given an input python source representation of data,
# run "python -c'id(DATA)'" under gdb with a breakpoint on
# builtin_id and scrape out gdb's representation of the "op"
# parameter, and verify that the gdb displays the same string
#
# Verify that the gdb displays the expected string
#
# For a nested structure, the first time we hit the breakpoint will
# give us the top-level structure
# NOTE: avoid decoding too much of the traceback as some
# undecodable characters may lurk there in optimized mode
# (issue #19743).
cmds_after_breakpoint = cmds_after_breakpoint or ["backtrace 1"]
gdb_output = self.get_stack_trace(source, breakpoint=BREAKPOINT_FN,
cmds_after_breakpoint=cmds_after_breakpoint,
import_site=import_site)
# gdb can insert additional '\n' and space characters in various places
# in its output, depending on the width of the terminal it's connected
# to (using its "wrap_here" function)
m = re.match(r'.*#0\s+builtin_id\s+\(self\=.*,\s+v=\s*(.*?)\)\s+at\s+\S*Python/bltinmodule.c.*',
gdb_output, re.DOTALL)
if not m:
self.fail('Unexpected gdb output: %r\n%s' % (gdb_output, gdb_output))
return m.group(1), gdb_output
def assertEndsWith(self, actual, exp_end):
'''Ensure that the given "actual" string ends with "exp_end"'''
self.assertTrue(actual.endswith(exp_end),
msg='%r did not end with %r' % (actual, exp_end))
def assertMultilineMatches(self, actual, pattern):
m = re.match(pattern, actual, re.DOTALL)
if not m:
self.fail(msg='%r did not match %r' % (actual, pattern))
def get_sample_script(self):
return findfile('gdb_sample.py')
class PrettyPrintTests(DebuggerTests):
def test_getting_backtrace(self):
gdb_output = self.get_stack_trace('id(42)')
self.assertTrue(BREAKPOINT_FN in gdb_output)
def assertGdbRepr(self, val, exp_repr=None):
# Ensure that gdb's rendering of the value in a debugged process
# matches repr(value) in this process:
gdb_repr, gdb_output = self.get_gdb_repr('id(' + ascii(val) + ')')
if not exp_repr:
exp_repr = repr(val)
self.assertEqual(gdb_repr, exp_repr,
('%r did not equal expected %r; full output was:\n%s'
% (gdb_repr, exp_repr, gdb_output)))
def test_int(self):
'Verify the pretty-printing of various int values'
self.assertGdbRepr(42)
self.assertGdbRepr(0)
self.assertGdbRepr(-7)
self.assertGdbRepr(1000000000000)
self.assertGdbRepr(-1000000000000000)
def test_singletons(self):
'Verify the pretty-printing of True, False and None'
self.assertGdbRepr(True)
self.assertGdbRepr(False)
self.assertGdbRepr(None)
def test_dicts(self):
'Verify the pretty-printing of dictionaries'
self.assertGdbRepr({})
self.assertGdbRepr({'foo': 'bar'}, "{'foo': 'bar'}")
# Python preserves insertion order since 3.6
self.assertGdbRepr({'foo': 'bar', 'douglas': 42}, "{'foo': 'bar', 'douglas': 42}")
def test_lists(self):
'Verify the pretty-printing of lists'
self.assertGdbRepr([])
self.assertGdbRepr(list(range(5)))
def test_bytes(self):
'Verify the pretty-printing of bytes'
self.assertGdbRepr(b'')
self.assertGdbRepr(b'And now for something hopefully the same')
self.assertGdbRepr(b'string with embedded NUL here \0 and then some more text')
self.assertGdbRepr(b'this is a tab:\t'
b' this is a slash-N:\n'
b' this is a slash-R:\r'
)
self.assertGdbRepr(b'this is byte 255:\xff and byte 128:\x80')
self.assertGdbRepr(bytes([b for b in range(255)]))
def test_strings(self):
'Verify the pretty-printing of unicode strings'
encoding = locale.getpreferredencoding()
def check_repr(text):
try:
text.encode(encoding)
printable = True
except UnicodeEncodeError:
self.assertGdbRepr(text, ascii(text))
else:
self.assertGdbRepr(text)
self.assertGdbRepr('')
self.assertGdbRepr('And now for something hopefully the same')
self.assertGdbRepr('string with embedded NUL here \0 and then some more text')
# Test printing a single character:
# U+2620 SKULL AND CROSSBONES
check_repr('\u2620')
# Test printing a Japanese unicode string
# (I believe this reads "mojibake", using 3 characters from the CJK
# Unified Ideographs area, followed by U+3051 HIRAGANA LETTER KE)
check_repr('\u6587\u5b57\u5316\u3051')
# Test a character outside the BMP:
# U+1D121 MUSICAL SYMBOL C CLEF
# This is:
# UTF-8: 0xF0 0x9D 0x84 0xA1
# UTF-16: 0xD834 0xDD21
check_repr(chr(0x1D121))
def test_tuples(self):
'Verify the pretty-printing of tuples'
self.assertGdbRepr(tuple(), '()')
self.assertGdbRepr((1,), '(1,)')
self.assertGdbRepr(('foo', 'bar', 'baz'))
def test_sets(self):
'Verify the pretty-printing of sets'
if (gdb_major_version, gdb_minor_version) < (7, 3):
self.skipTest("pretty-printing of sets needs gdb 7.3 or later")
self.assertGdbRepr(set(), "set()")
self.assertGdbRepr(set(['a']), "{'a'}")
# PYTHONHASHSEED is need to get the exact frozenset item order
if not sys.flags.ignore_environment:
self.assertGdbRepr(set(['a', 'b']), "{'a', 'b'}")
self.assertGdbRepr(set([4, 5, 6]), "{4, 5, 6}")
# Ensure that we handle sets containing the "dummy" key value,
# which happens on deletion:
gdb_repr, gdb_output = self.get_gdb_repr('''s = set(['a','b'])
s.remove('a')
id(s)''')
self.assertEqual(gdb_repr, "{'b'}")
def test_frozensets(self):
'Verify the pretty-printing of frozensets'
if (gdb_major_version, gdb_minor_version) < (7, 3):
self.skipTest("pretty-printing of frozensets needs gdb 7.3 or later")
self.assertGdbRepr(frozenset(), "frozenset()")
self.assertGdbRepr(frozenset(['a']), "frozenset({'a'})")
# PYTHONHASHSEED is need to get the exact frozenset item order
if not sys.flags.ignore_environment:
self.assertGdbRepr(frozenset(['a', 'b']), "frozenset({'a', 'b'})")
self.assertGdbRepr(frozenset([4, 5, 6]), "frozenset({4, 5, 6})")
def test_exceptions(self):
# Test a RuntimeError
gdb_repr, gdb_output = self.get_gdb_repr('''
try:
raise RuntimeError("I am an error")
except RuntimeError as e:
id(e)
''')
self.assertEqual(gdb_repr,
"RuntimeError('I am an error',)")
# Test division by zero:
gdb_repr, gdb_output = self.get_gdb_repr('''
try:
a = 1 / 0
except ZeroDivisionError as e:
id(e)
''')
self.assertEqual(gdb_repr,
"ZeroDivisionError('division by zero',)")
def test_modern_class(self):
'Verify the pretty-printing of new-style class instances'
gdb_repr, gdb_output = self.get_gdb_repr('''
class Foo:
pass
foo = Foo()
foo.an_int = 42
id(foo)''')
m = re.match(r'<Foo\(an_int=42\) at remote 0x-?[0-9a-f]+>', gdb_repr)
self.assertTrue(m,
msg='Unexpected new-style class rendering %r' % gdb_repr)
def test_subclassing_list(self):
'Verify the pretty-printing of an instance of a list subclass'
gdb_repr, gdb_output = self.get_gdb_repr('''
class Foo(list):
pass
foo = Foo()
foo += [1, 2, 3]
foo.an_int = 42
id(foo)''')
m = re.match(r'<Foo\(an_int=42\) at remote 0x-?[0-9a-f]+>', gdb_repr)
self.assertTrue(m,
msg='Unexpected new-style class rendering %r' % gdb_repr)
def test_subclassing_tuple(self):
'Verify the pretty-printing of an instance of a tuple subclass'
# This should exercise the negative tp_dictoffset code in the
# new-style class support
gdb_repr, gdb_output = self.get_gdb_repr('''
class Foo(tuple):
pass
foo = Foo((1, 2, 3))
foo.an_int = 42
id(foo)''')
m = re.match(r'<Foo\(an_int=42\) at remote 0x-?[0-9a-f]+>', gdb_repr)
self.assertTrue(m,
msg='Unexpected new-style class rendering %r' % gdb_repr)
def assertSane(self, source, corruption, exprepr=None):
'''Run Python under gdb, corrupting variables in the inferior process
immediately before taking a backtrace.
Verify that the variable's representation is the expected failsafe
representation'''
if corruption:
cmds_after_breakpoint=[corruption, 'backtrace']
else:
cmds_after_breakpoint=['backtrace']
gdb_repr, gdb_output = \
self.get_gdb_repr(source,
cmds_after_breakpoint=cmds_after_breakpoint)
if exprepr:
if gdb_repr == exprepr:
# gdb managed to print the value in spite of the corruption;
# this is good (see http://bugs.python.org/issue8330)
return
# Match anything for the type name; 0xDEADBEEF could point to
# something arbitrary (see http://bugs.python.org/issue8330)
pattern = '<.* at remote 0x-?[0-9a-f]+>'
m = re.match(pattern, gdb_repr)
if not m:
self.fail('Unexpected gdb representation: %r\n%s' % \
(gdb_repr, gdb_output))
def test_NULL_ptr(self):
'Ensure that a NULL PyObject* is handled gracefully'
gdb_repr, gdb_output = (
self.get_gdb_repr('id(42)',
cmds_after_breakpoint=['set variable v=0',
'backtrace'])
)
self.assertEqual(gdb_repr, '0x0')
def test_NULL_ob_type(self):
'Ensure that a PyObject* with NULL ob_type is handled gracefully'
self.assertSane('id(42)',
'set v->ob_type=0')
def test_corrupt_ob_type(self):
'Ensure that a PyObject* with a corrupt ob_type is handled gracefully'
self.assertSane('id(42)',
'set v->ob_type=0xDEADBEEF',
exprepr='42')
def test_corrupt_tp_flags(self):
'Ensure that a PyObject* with a type with corrupt tp_flags is handled'
self.assertSane('id(42)',
'set v->ob_type->tp_flags=0x0',
exprepr='42')
def test_corrupt_tp_name(self):
'Ensure that a PyObject* with a type with corrupt tp_name is handled'
self.assertSane('id(42)',
'set v->ob_type->tp_name=0xDEADBEEF',
exprepr='42')
def test_builtins_help(self):
'Ensure that the new-style class _Helper in site.py can be handled'
if sys.flags.no_site:
self.skipTest("need site module, but -S option was used")
# (this was the issue causing tracebacks in
# http://bugs.python.org/issue8032#msg100537 )
gdb_repr, gdb_output = self.get_gdb_repr('id(__builtins__.help)', import_site=True)
m = re.match(r'<_Helper at remote 0x-?[0-9a-f]+>', gdb_repr)
self.assertTrue(m,
msg='Unexpected rendering %r' % gdb_repr)
def test_selfreferential_list(self):
'''Ensure that a reference loop involving a list doesn't lead proxyval
into an infinite loop:'''
gdb_repr, gdb_output = \
self.get_gdb_repr("a = [3, 4, 5] ; a.append(a) ; id(a)")
self.assertEqual(gdb_repr, '[3, 4, 5, [...]]')
gdb_repr, gdb_output = \
self.get_gdb_repr("a = [3, 4, 5] ; b = [a] ; a.append(b) ; id(a)")
self.assertEqual(gdb_repr, '[3, 4, 5, [[...]]]')
def test_selfreferential_dict(self):
'''Ensure that a reference loop involving a dict doesn't lead proxyval
into an infinite loop:'''
gdb_repr, gdb_output = \
self.get_gdb_repr("a = {} ; b = {'bar':a} ; a['foo'] = b ; id(a)")
self.assertEqual(gdb_repr, "{'foo': {'bar': {...}}}")
def test_selfreferential_old_style_instance(self):
gdb_repr, gdb_output = \
self.get_gdb_repr('''
class Foo:
pass
foo = Foo()
foo.an_attr = foo
id(foo)''')
self.assertTrue(re.match(r'<Foo\(an_attr=<\.\.\.>\) at remote 0x-?[0-9a-f]+>',
gdb_repr),
'Unexpected gdb representation: %r\n%s' % \
(gdb_repr, gdb_output))
def test_selfreferential_new_style_instance(self):
gdb_repr, gdb_output = \
self.get_gdb_repr('''
class Foo(object):
pass
foo = Foo()
foo.an_attr = foo
id(foo)''')
self.assertTrue(re.match(r'<Foo\(an_attr=<\.\.\.>\) at remote 0x-?[0-9a-f]+>',
gdb_repr),
'Unexpected gdb representation: %r\n%s' % \
(gdb_repr, gdb_output))
gdb_repr, gdb_output = \
self.get_gdb_repr('''
class Foo(object):
pass
a = Foo()
b = Foo()
a.an_attr = b
b.an_attr = a
id(a)''')
self.assertTrue(re.match(r'<Foo\(an_attr=<Foo\(an_attr=<\.\.\.>\) at remote 0x-?[0-9a-f]+>\) at remote 0x-?[0-9a-f]+>',
gdb_repr),
'Unexpected gdb representation: %r\n%s' % \
(gdb_repr, gdb_output))
def test_truncation(self):
'Verify that very long output is truncated'
gdb_repr, gdb_output = self.get_gdb_repr('id(list(range(1000)))')
self.assertEqual(gdb_repr,
"[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, "
"14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, "
"27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, "
"40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, "
"53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, "
"66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, "
"79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, "
"92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, "
"104, 105, 106, 107, 108, 109, 110, 111, 112, 113, "
"114, 115, 116, 117, 118, 119, 120, 121, 122, 123, "
"124, 125, 126, 127, 128, 129, 130, 131, 132, 133, "
"134, 135, 136, 137, 138, 139, 140, 141, 142, 143, "
"144, 145, 146, 147, 148, 149, 150, 151, 152, 153, "
"154, 155, 156, 157, 158, 159, 160, 161, 162, 163, "
"164, 165, 166, 167, 168, 169, 170, 171, 172, 173, "
"174, 175, 176, 177, 178, 179, 180, 181, 182, 183, "
"184, 185, 186, 187, 188, 189, 190, 191, 192, 193, "
"194, 195, 196, 197, 198, 199, 200, 201, 202, 203, "
"204, 205, 206, 207, 208, 209, 210, 211, 212, 213, "
"214, 215, 216, 217, 218, 219, 220, 221, 222, 223, "
"224, 225, 226...(truncated)")
self.assertEqual(len(gdb_repr),
1024 + len('...(truncated)'))
def test_builtin_method(self):
gdb_repr, gdb_output = self.get_gdb_repr('import sys; id(sys.stdout.readlines)')
self.assertTrue(re.match(r'<built-in method readlines of _io.TextIOWrapper object at remote 0x-?[0-9a-f]+>',
gdb_repr),
'Unexpected gdb representation: %r\n%s' % \
(gdb_repr, gdb_output))
def test_frames(self):
gdb_output = self.get_stack_trace('''
def foo(a, b, c):
pass
foo(3, 4, 5)
id(foo.__code__)''',
breakpoint='builtin_id',
cmds_after_breakpoint=['print (PyFrameObject*)(((PyCodeObject*)v)->co_zombieframe)']
)
self.assertTrue(re.match(r'.*\s+\$1 =\s+Frame 0x-?[0-9a-f]+, for file <string>, line 3, in foo \(\)\s+.*',
gdb_output,
re.DOTALL),
'Unexpected gdb representation: %r\n%s' % (gdb_output, gdb_output))
@unittest.skipIf(python_is_optimized(),
"Python was compiled with optimizations")
class PyListTests(DebuggerTests):
def assertListing(self, expected, actual):
self.assertEndsWith(actual, expected)
def test_basic_command(self):
'Verify that the "py-list" command works'
bt = self.get_stack_trace(script=self.get_sample_script(),
cmds_after_breakpoint=['py-list'])
self.assertListing(' 5 \n'
' 6 def bar(a, b, c):\n'
' 7 baz(a, b, c)\n'
' 8 \n'
' 9 def baz(*args):\n'
' >10 id(42)\n'
' 11 \n'
' 12 foo(1, 2, 3)\n',
bt)
def test_one_abs_arg(self):
'Verify the "py-list" command with one absolute argument'
bt = self.get_stack_trace(script=self.get_sample_script(),
cmds_after_breakpoint=['py-list 9'])
self.assertListing(' 9 def baz(*args):\n'
' >10 id(42)\n'
' 11 \n'
' 12 foo(1, 2, 3)\n',
bt)
def test_two_abs_args(self):
'Verify the "py-list" command with two absolute arguments'
bt = self.get_stack_trace(script=self.get_sample_script(),
cmds_after_breakpoint=['py-list 1,3'])
self.assertListing(' 1 # Sample script for use by test_gdb.py\n'
' 2 \n'
' 3 def foo(a, b, c):\n',
bt)
class StackNavigationTests(DebuggerTests):
@unittest.skipUnless(HAS_PYUP_PYDOWN, "test requires py-up/py-down commands")
@unittest.skipIf(python_is_optimized(),
"Python was compiled with optimizations")
def test_pyup_command(self):
'Verify that the "py-up" command works'
bt = self.get_stack_trace(script=self.get_sample_script(),
cmds_after_breakpoint=['py-up', 'py-up'])
self.assertMultilineMatches(bt,
r'''^.*
#[0-9]+ Frame 0x-?[0-9a-f]+, for file .*gdb_sample.py, line 7, in bar \(a=1, b=2, c=3\)
baz\(a, b, c\)
$''')
@unittest.skipUnless(HAS_PYUP_PYDOWN, "test requires py-up/py-down commands")
def test_down_at_bottom(self):
'Verify handling of "py-down" at the bottom of the stack'
bt = self.get_stack_trace(script=self.get_sample_script(),
cmds_after_breakpoint=['py-down'])
self.assertEndsWith(bt,
'Unable to find a newer python frame\n')
@unittest.skipUnless(HAS_PYUP_PYDOWN, "test requires py-up/py-down commands")
def test_up_at_top(self):
'Verify handling of "py-up" at the top of the stack'
bt = self.get_stack_trace(script=self.get_sample_script(),
cmds_after_breakpoint=['py-up'] * 5)
self.assertEndsWith(bt,
'Unable to find an older python frame\n')
@unittest.skipUnless(HAS_PYUP_PYDOWN, "test requires py-up/py-down commands")
@unittest.skipIf(python_is_optimized(),
"Python was compiled with optimizations")
def test_up_then_down(self):
'Verify "py-up" followed by "py-down"'
bt = self.get_stack_trace(script=self.get_sample_script(),
cmds_after_breakpoint=['py-up', 'py-up', 'py-down'])
self.assertMultilineMatches(bt,
r'''^.*
#[0-9]+ Frame 0x-?[0-9a-f]+, for file .*gdb_sample.py, line 7, in bar \(a=1, b=2, c=3\)
baz\(a, b, c\)
#[0-9]+ Frame 0x-?[0-9a-f]+, for file .*gdb_sample.py, line 10, in baz \(args=\(1, 2, 3\)\)
id\(42\)
$''')
class PyBtTests(DebuggerTests):
@unittest.skipIf(python_is_optimized(),
"Python was compiled with optimizations")
def test_bt(self):
'Verify that the "py-bt" command works'
bt = self.get_stack_trace(script=self.get_sample_script(),
cmds_after_breakpoint=['py-bt'])
self.assertMultilineMatches(bt,
r'''^.*
Traceback \(most recent call first\):
<built-in method id of module object .*>
File ".*gdb_sample.py", line 10, in baz
id\(42\)
File ".*gdb_sample.py", line 7, in bar
baz\(a, b, c\)
File ".*gdb_sample.py", line 4, in foo
bar\(a, b, c\)
File ".*gdb_sample.py", line 12, in <module>
foo\(1, 2, 3\)
''')
@unittest.skipIf(python_is_optimized(),
"Python was compiled with optimizations")
def test_bt_full(self):
'Verify that the "py-bt-full" command works'
bt = self.get_stack_trace(script=self.get_sample_script(),
cmds_after_breakpoint=['py-bt-full'])
self.assertMultilineMatches(bt,
r'''^.*
#[0-9]+ Frame 0x-?[0-9a-f]+, for file .*gdb_sample.py, line 7, in bar \(a=1, b=2, c=3\)
baz\(a, b, c\)
#[0-9]+ Frame 0x-?[0-9a-f]+, for file .*gdb_sample.py, line 4, in foo \(a=1, b=2, c=3\)
bar\(a, b, c\)
#[0-9]+ Frame 0x-?[0-9a-f]+, for file .*gdb_sample.py, line 12, in <module> \(\)
foo\(1, 2, 3\)
''')
@unittest.skipUnless(_thread,
"Python was compiled without thread support")
def test_threads(self):
'Verify that "py-bt" indicates threads that are waiting for the GIL'
cmd = '''
from threading import Thread
class TestThread(Thread):
# These threads would run forever, but we'll interrupt things with the
# debugger
def run(self):
i = 0
while 1:
i += 1
t = {}
for i in range(4):
t[i] = TestThread()
t[i].start()
# Trigger a breakpoint on the main thread
id(42)
'''
# Verify with "py-bt":
gdb_output = self.get_stack_trace(cmd,
cmds_after_breakpoint=['thread apply all py-bt'])
self.assertIn('Waiting for the GIL', gdb_output)
# Verify with "py-bt-full":
gdb_output = self.get_stack_trace(cmd,
cmds_after_breakpoint=['thread apply all py-bt-full'])
self.assertIn('Waiting for the GIL', gdb_output)
@unittest.skipIf(python_is_optimized(),
"Python was compiled with optimizations")
# Some older versions of gdb will fail with
# "Cannot find new threads: generic error"
# unless we add LD_PRELOAD=PATH-TO-libpthread.so.1 as a workaround
@unittest.skipUnless(_thread,
"Python was compiled without thread support")
def test_gc(self):
'Verify that "py-bt" indicates if a thread is garbage-collecting'
cmd = ('from gc import collect\n'
'id(42)\n'
'def foo():\n'
' collect()\n'
'def bar():\n'
' foo()\n'
'bar()\n')
# Verify with "py-bt":
gdb_output = self.get_stack_trace(cmd,
cmds_after_breakpoint=['break update_refs', 'continue', 'py-bt'],
)
self.assertIn('Garbage-collecting', gdb_output)
# Verify with "py-bt-full":
gdb_output = self.get_stack_trace(cmd,
cmds_after_breakpoint=['break update_refs', 'continue', 'py-bt-full'],
)
self.assertIn('Garbage-collecting', gdb_output)
@unittest.skipIf(python_is_optimized(),
"Python was compiled with optimizations")
# Some older versions of gdb will fail with
# "Cannot find new threads: generic error"
# unless we add LD_PRELOAD=PATH-TO-libpthread.so.1 as a workaround
@unittest.skipUnless(_thread,
"Python was compiled without thread support")
def test_pycfunction(self):
'Verify that "py-bt" displays invocations of PyCFunction instances'
# Tested function must not be defined with METH_NOARGS or METH_O,
# otherwise call_function() doesn't call PyCFunction_Call()
cmd = ('from time import gmtime\n'
'def foo():\n'
' gmtime(1)\n'
'def bar():\n'
' foo()\n'
'bar()\n')
# Verify with "py-bt":
gdb_output = self.get_stack_trace(cmd,
breakpoint='time_gmtime',
cmds_after_breakpoint=['bt', 'py-bt'],
)
self.assertIn('<built-in method gmtime', gdb_output)
# Verify with "py-bt-full":
gdb_output = self.get_stack_trace(cmd,
breakpoint='time_gmtime',
cmds_after_breakpoint=['py-bt-full'],
)
self.assertIn('#2 <built-in method gmtime', gdb_output)
@unittest.skipIf(python_is_optimized(),
"Python was compiled with optimizations")
def test_wrapper_call(self):
cmd = textwrap.dedent('''
class MyList(list):
def __init__(self):
super().__init__() # wrapper_call()
id("first break point")
l = MyList()
''')
# Verify with "py-bt":
gdb_output = self.get_stack_trace(cmd,
cmds_after_breakpoint=['break wrapper_call', 'continue', 'py-bt'])
self.assertRegex(gdb_output,
r"<method-wrapper u?'__init__' of MyList object at ")
class PyPrintTests(DebuggerTests):
@unittest.skipIf(python_is_optimized(),
"Python was compiled with optimizations")
def test_basic_command(self):
'Verify that the "py-print" command works'
bt = self.get_stack_trace(script=self.get_sample_script(),
cmds_after_breakpoint=['py-up', 'py-print args'])
self.assertMultilineMatches(bt,
r".*\nlocal 'args' = \(1, 2, 3\)\n.*")
@unittest.skipIf(python_is_optimized(),
"Python was compiled with optimizations")
@unittest.skipUnless(HAS_PYUP_PYDOWN, "test requires py-up/py-down commands")
def test_print_after_up(self):
bt = self.get_stack_trace(script=self.get_sample_script(),
cmds_after_breakpoint=['py-up', 'py-up', 'py-print c', 'py-print b', 'py-print a'])
self.assertMultilineMatches(bt,
r".*\nlocal 'c' = 3\nlocal 'b' = 2\nlocal 'a' = 1\n.*")
@unittest.skipIf(python_is_optimized(),
"Python was compiled with optimizations")
def test_printing_global(self):
bt = self.get_stack_trace(script=self.get_sample_script(),
cmds_after_breakpoint=['py-up', 'py-print __name__'])
self.assertMultilineMatches(bt,
r".*\nglobal '__name__' = '__main__'\n.*")
@unittest.skipIf(python_is_optimized(),
"Python was compiled with optimizations")
def test_printing_builtin(self):
bt = self.get_stack_trace(script=self.get_sample_script(),
cmds_after_breakpoint=['py-up', 'py-print len'])
self.assertMultilineMatches(bt,
r".*\nbuiltin 'len' = <built-in method len of module object at remote 0x-?[0-9a-f]+>\n.*")
class PyLocalsTests(DebuggerTests):
@unittest.skipIf(python_is_optimized(),
"Python was compiled with optimizations")
def test_basic_command(self):
bt = self.get_stack_trace(script=self.get_sample_script(),
cmds_after_breakpoint=['py-up', 'py-locals'])
self.assertMultilineMatches(bt,
r".*\nargs = \(1, 2, 3\)\n.*")
@unittest.skipUnless(HAS_PYUP_PYDOWN, "test requires py-up/py-down commands")
@unittest.skipIf(python_is_optimized(),
"Python was compiled with optimizations")
def test_locals_after_up(self):
bt = self.get_stack_trace(script=self.get_sample_script(),
cmds_after_breakpoint=['py-up', 'py-up', 'py-locals'])
self.assertMultilineMatches(bt,
r".*\na = 1\nb = 2\nc = 3\n.*")
def test_main():
if support.verbose:
print("GDB version %s.%s:" % (gdb_major_version, gdb_minor_version))
for line in gdb_version.splitlines():
print(" " * 4 + line)
run_unittest(PrettyPrintTests,
PyListTests,
StackNavigationTests,
PyBtTests,
PyPrintTests,
PyLocalsTests
)
if __name__ == "__main__":
test_main()

View File

@ -0,0 +1 @@
https://github.com/akheron/cpython/raw/master/Lib/test/test_gdb.py

1885
toxygen/tests/tests_socks.py Normal file

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,17 @@
# -*- mode: python; indent-tabs-mode: nil; py-indent-offset: 4; coding: utf-8 -*-
from notifications import sound
from notifications.sound import SOUND_NOTIFICATION
from time import sleep
if True:
def test_sound_notification(self):
"""
Plays sound notification
:param type of notification
"""
sound.sound_notification( SOUND_NOTIFICATION['MESSAGE'] )
sleep(10)
sound.sound_notification( SOUND_NOTIFICATION['FILE_TRANSFER'] )
sleep(10)
sound.sound_notification( None )

View File

@ -90,7 +90,7 @@ class MainWindow(QtWidgets.QMainWindow):
self.list_buffers.setSizePolicy(QtWidgets.QSizePolicy.Preferred,
QtWidgets.QSizePolicy.Preferred)
self.stacked_buffers.setSizePolicy(QtWidgets.QSizePolicy.Expanding,
QtWidgets.QSizePolicy.Expanding)
QtWidgets.QSizePolicy.Expanding)
# MainWindow
self.setCentralWidget(splitter)
@ -191,7 +191,7 @@ class MainWindow(QtWidgets.QMainWindow):
self.actions['preferences'],
self.actions['about'],
self.actions['quit']])
self.toolbar = toolbar
self.toolbar = toolbar
self.buffers[0].widget.input.setFocus()
# open debug dialog

View File

@ -98,9 +98,7 @@ class MessagesItemsFactory:
return item
# -----------------------------------------------------------------------------------------------------------------
# Private methods
# -----------------------------------------------------------------------------------------------------------------
def _create_message_browser(self, text, width, message_type, parent=None):
return MessageBrowser(self._settings, self._message_edit, self._smiley_loader, self._plugin_loader,

View File

@ -3,7 +3,7 @@ import os
from PyQt5 import uic
from PyQt5 import QtCore, QtGui, QtWidgets
from qtpy.QtGui import (QColor, QTextCharFormat, QFont, QSyntaxHighlighter)
from qtpy.QtGui import (QColor, QTextCharFormat, QFont, QSyntaxHighlighter, QFontMetrics)
from ui.contact_items import *
from ui.widgets import MultilineEdit
@ -652,9 +652,7 @@ class MainWindow(QtWidgets.QMainWindow):
else:
super().keyPressEvent(event)
# -----------------------------------------------------------------------------------------------------------------
# Functions which called when user click in menu
# -----------------------------------------------------------------------------------------------------------------
def log_console(self):
self._me.show()
@ -759,7 +757,7 @@ class MainWindow(QtWidgets.QMainWindow):
self._we.list_buffers.setSizePolicy(QtWidgets.QSizePolicy.Preferred,
QtWidgets.QSizePolicy.Preferred)
self._we.stacked_buffers.setSizePolicy(QtWidgets.QSizePolicy.Expanding,
QtWidgets.QSizePolicy.Expanding)
QtWidgets.QSizePolicy.Expanding)
LOG.info("Showing WeechatConsole")
self._we.show()
@ -877,9 +875,7 @@ class MainWindow(QtWidgets.QMainWindow):
120))
self.menu.show()
# -----------------------------------------------------------------------------------------------------------------
# Messages, calls and file transfers
# -----------------------------------------------------------------------------------------------------------------
def send_message(self):
self._messenger.send_message()
@ -942,9 +938,7 @@ class MainWindow(QtWidgets.QMainWindow):
self.videocallButton.setIcon(icon)
self.videocallButton.setIconSize(QtCore.QSize(35, 35))
# -----------------------------------------------------------------------------------------------------------------
# Functions which called when user open context menu in friends list
# -----------------------------------------------------------------------------------------------------------------
def _friend_right_click(self, pos):
item = self.friends_list.itemAt(pos)
@ -1001,9 +995,7 @@ class MainWindow(QtWidgets.QMainWindow):
def select_contact_row(self, row_index):
self.friends_list.setCurrentRow(row_index)
# -----------------------------------------------------------------------------------------------------------------
# Functions which called when user click somewhere else
# -----------------------------------------------------------------------------------------------------------------
def _selected_contact_changed(self):
num = self.friends_list.currentRow()

View File

@ -48,7 +48,7 @@ class MessageBrowser(QtWidgets.QTextBrowser):
# resize(self, a0: QSize): argument 1 has unexpected type 'int'
# resize(self, w: int, h: int): argument 2 has unexpected type 'float'
pass
self.setTextInteractionFlags(QtCore.Qt.TextSelectableByMouse | QtCore.Qt.LinksAccessibleByMouse)
self.anchorClicked.connect(self.on_anchor_clicked)

View File

@ -0,0 +1,99 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>Form</class>
<widget class="QWidget" name="Form">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>560</width>
<height>320</height>
</rect>
</property>
<property name="minimumSize">
<size>
<width>560</width>
<height>320</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>560</width>
<height>320</height>
</size>
</property>
<property name="windowTitle">
<string>Form</string>
</property>
<widget class="QLabel" name="toxIdLabel">
<property name="geometry">
<rect>
<x>50</x>
<y>10</y>
<width>150</width>
<height>20</height>
</rect>
</property>
<property name="text">
<string>TextLabel</string>
</property>
</widget>
<widget class="QLabel" name="messageLabel">
<property name="geometry">
<rect>
<x>50</x>
<y>70</y>
<width>150</width>
<height>30</height>
</rect>
</property>
<property name="text">
<string>TextLabel</string>
</property>
</widget>
<widget class="QPlainTextEdit" name="messagePlainTextEdit">
<property name="geometry">
<rect>
<x>50</x>
<y>110</y>
<width>460</width>
<height>150</height>
</rect>
</property>
</widget>
<widget class="QPushButton" name="addBootstrapPushButton">
<property name="geometry">
<rect>
<x>50</x>
<y>270</y>
<width>460</width>
<height>30</height>
</rect>
</property>
<property name="text">
<string>PushButton</string>
</property>
</widget>
<widget class="QLabel" name="errorLabel">
<property name="enabled">
<bool>true</bool>
</property>
<property name="geometry">
<rect>
<x>220</x>
<y>10</y>
<width>321</width>
<height>31</height>
</rect>
</property>
<property name="contextMenuPolicy">
<enum>Qt::NoContextMenu</enum>
</property>
<property name="text">
<string/>
</property>
</widget>
</widget>
<resources/>
<connections/>
</ui>

View File

@ -0,0 +1,99 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>Form</class>
<widget class="QWidget" name="Form">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>560</width>
<height>320</height>
</rect>
</property>
<property name="minimumSize">
<size>
<width>560</width>
<height>320</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>560</width>
<height>320</height>
</size>
</property>
<property name="windowTitle">
<string>Form</string>
</property>
<widget class="QLabel" name="toxIdLabel">
<property name="geometry">
<rect>
<x>50</x>
<y>10</y>
<width>150</width>
<height>20</height>
</rect>
</property>
<property name="text">
<string>TextLabel</string>
</property>
</widget>
<widget class="QLabel" name="messageLabel">
<property name="geometry">
<rect>
<x>50</x>
<y>70</y>
<width>150</width>
<height>30</height>
</rect>
</property>
<property name="text">
<string>TextLabel</string>
</property>
</widget>
<widget class="QPlainTextEdit" name="messagePlainTextEdit">
<property name="geometry">
<rect>
<x>50</x>
<y>110</y>
<width>460</width>
<height>150</height>
</rect>
</property>
</widget>
<widget class="QPushButton" name="addContactPushButton">
<property name="geometry">
<rect>
<x>50</x>
<y>270</y>
<width>460</width>
<height>30</height>
</rect>
</property>
<property name="text">
<string>PushButton</string>
</property>
</widget>
<widget class="QLabel" name="errorLabel">
<property name="enabled">
<bool>true</bool>
</property>
<property name="geometry">
<rect>
<x>220</x>
<y>10</y>
<width>321</width>
<height>31</height>
</rect>
</property>
<property name="contextMenuPolicy">
<enum>Qt::NoContextMenu</enum>
</property>
<property name="text">
<string/>
</property>
</widget>
</widget>
<resources/>
<connections/>
</ui>

View File

@ -0,0 +1,87 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>Form</class>
<widget class="QWidget" name="Form">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>315</width>
<height>218</height>
</rect>
</property>
<property name="minimumSize">
<size>
<width>315</width>
<height>218</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>315</width>
<height>218</height>
</size>
</property>
<property name="windowTitle">
<string>Form</string>
</property>
<widget class="QLabel" name="inputDeviceLabel">
<property name="geometry">
<rect>
<x>30</x>
<y>10</y>
<width>261</width>
<height>30</height>
</rect>
</property>
<property name="font">
<font>
<pointsize>16</pointsize>
</font>
</property>
<property name="text">
<string>TextLabel</string>
</property>
</widget>
<widget class="QLabel" name="outputDeviceLabel">
<property name="geometry">
<rect>
<x>30</x>
<y>100</y>
<width>261</width>
<height>30</height>
</rect>
</property>
<property name="font">
<font>
<pointsize>16</pointsize>
</font>
</property>
<property name="text">
<string>TextLabel</string>
</property>
</widget>
<widget class="QComboBox" name="inputDeviceComboBox">
<property name="geometry">
<rect>
<x>30</x>
<y>50</y>
<width>255</width>
<height>41</height>
</rect>
</property>
</widget>
<widget class="QComboBox" name="outputDeviceComboBox">
<property name="geometry">
<rect>
<x>30</x>
<y>140</y>
<width>255</width>
<height>41</height>
</rect>
</property>
</widget>
</widget>
<resources/>
<connections/>
</ui>

View File

@ -0,0 +1,29 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>Form</class>
<widget class="QWidget" name="Form">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>500</width>
<height>375</height>
</rect>
</property>
<property name="windowTitle">
<string>Form</string>
</property>
<widget class="QListWidget" name="bansListWidget">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>500</width>
<height>375</height>
</rect>
</property>
</widget>
</widget>
<resources/>
<connections/>
</ui>

View File

@ -0,0 +1,127 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>Form</class>
<widget class="QWidget" name="Form">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>640</width>
<height>300</height>
</rect>
</property>
<property name="windowTitle">
<string>Form</string>
</property>
<widget class="QPushButton" name="addGroupButton">
<property name="enabled">
<bool>false</bool>
</property>
<property name="geometry">
<rect>
<x>20</x>
<y>250</y>
<width>601</width>
<height>41</height>
</rect>
</property>
<property name="text">
<string/>
</property>
</widget>
<widget class="QLineEdit" name="groupNameLineEdit">
<property name="geometry">
<rect>
<x>150</x>
<y>20</y>
<width>470</width>
<height>35</height>
</rect>
</property>
</widget>
<widget class="QComboBox" name="groupTypeComboBox">
<property name="geometry">
<rect>
<x>150</x>
<y>80</y>
<width>470</width>
<height>35</height>
</rect>
</property>
</widget>
<widget class="QLabel" name="groupNameLabel">
<property name="geometry">
<rect>
<x>20</x>
<y>20</y>
<width>121</width>
<height>31</height>
</rect>
</property>
<property name="text">
<string>TextLabel</string>
</property>
</widget>
<widget class="QLabel" name="groupTypeLabel">
<property name="geometry">
<rect>
<x>20</x>
<y>80</y>
<width>121</width>
<height>31</height>
</rect>
</property>
<property name="text">
<string>TextLabel</string>
</property>
</widget>
<widget class="QLabel" name="statusLabel">
<property name="geometry">
<rect>
<x>20</x>
<y>200</y>
<width>111</width>
<height>17</height>
</rect>
</property>
<property name="text">
<string>TextLabel</string>
</property>
</widget>
<widget class="QLabel" name="nickLabel">
<property name="geometry">
<rect>
<x>20</x>
<y>150</y>
<width>111</width>
<height>17</height>
</rect>
</property>
<property name="text">
<string>TextLabel</string>
</property>
</widget>
<widget class="QLineEdit" name="nickLineEdit">
<property name="geometry">
<rect>
<x>150</x>
<y>140</y>
<width>470</width>
<height>35</height>
</rect>
</property>
</widget>
<widget class="QComboBox" name="statusComboBox">
<property name="geometry">
<rect>
<x>150</x>
<y>190</y>
<width>470</width>
<height>35</height>
</rect>
</property>
</widget>
</widget>
<resources/>
<connections/>
</ui>

View File

@ -0,0 +1,128 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>Form</class>
<widget class="QWidget" name="Form">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>400</width>
<height>340</height>
</rect>
</property>
<property name="minimumSize">
<size>
<width>400</width>
<height>340</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>400</width>
<height>340</height>
</size>
</property>
<property name="windowTitle">
<string>Form</string>
</property>
<widget class="QPushButton" name="createProfile">
<property name="geometry">
<rect>
<x>30</x>
<y>270</y>
<width>341</width>
<height>51</height>
</rect>
</property>
<property name="text">
<string>PushButton</string>
</property>
</widget>
<widget class="QLineEdit" name="confirmPassword">
<property name="geometry">
<rect>
<x>30</x>
<y>170</y>
<width>341</width>
<height>41</height>
</rect>
</property>
<property name="echoMode">
<enum>QLineEdit::Password</enum>
</property>
</widget>
<widget class="QLineEdit" name="password">
<property name="geometry">
<rect>
<x>30</x>
<y>120</y>
<width>341</width>
<height>41</height>
</rect>
</property>
<property name="echoMode">
<enum>QLineEdit::Password</enum>
</property>
</widget>
<widget class="QLabel" name="passwordLabel">
<property name="geometry">
<rect>
<x>30</x>
<y>80</y>
<width>330</width>
<height>20</height>
</rect>
</property>
<property name="text">
<string>TextLabel</string>
</property>
</widget>
<widget class="QRadioButton" name="defaultFolder">
<property name="geometry">
<rect>
<x>30</x>
<y>10</y>
<width>330</width>
<height>23</height>
</rect>
</property>
<property name="text">
<string>RadioButton</string>
</property>
<property name="checked">
<bool>true</bool>
</property>
</widget>
<widget class="QRadioButton" name="programFolder">
<property name="geometry">
<rect>
<x>30</x>
<y>40</y>
<width>330</width>
<height>23</height>
</rect>
</property>
<property name="text">
<string>RadioButton</string>
</property>
</widget>
<widget class="QLabel" name="errorLabel">
<property name="geometry">
<rect>
<x>30</x>
<y>220</y>
<width>341</width>
<height>30</height>
</rect>
</property>
<property name="text">
<string/>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
</widget>
</widget>
<resources/>
<connections/>
</ui>

View File

@ -0,0 +1,58 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>Form</class>
<widget class="QWidget" name="Form">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>500</width>
<height>100</height>
</rect>
</property>
<property name="windowTitle">
<string>Form</string>
</property>
<widget class="QPushButton" name="cancelPushButton">
<property name="geometry">
<rect>
<x>330</x>
<y>30</y>
<width>161</width>
<height>41</height>
</rect>
</property>
<property name="text">
<string>PushButton</string>
</property>
</widget>
<widget class="QLabel" name="banTargetLabel">
<property name="geometry">
<rect>
<x>15</x>
<y>20</y>
<width>305</width>
<height>20</height>
</rect>
</property>
<property name="text">
<string>TextLabel</string>
</property>
</widget>
<widget class="QLabel" name="banTimeLabel">
<property name="geometry">
<rect>
<x>15</x>
<y>50</y>
<width>305</width>
<height>20</height>
</rect>
</property>
<property name="text">
<string>TextLabel</string>
</property>
</widget>
</widget>
<resources/>
<connections/>
</ui>

View File

@ -0,0 +1,71 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>Form</class>
<widget class="QWidget" name="Form">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>600</width>
<height>150</height>
</rect>
</property>
<property name="windowTitle">
<string>Form</string>
</property>
<widget class="QLabel" name="friendNameLabel">
<property name="geometry">
<rect>
<x>250</x>
<y>30</y>
<width>300</width>
<height>21</height>
</rect>
</property>
<property name="text">
<string>TextLabel</string>
</property>
</widget>
<widget class="QLabel" name="groupNameLabel">
<property name="geometry">
<rect>
<x>250</x>
<y>70</y>
<width>300</width>
<height>21</height>
</rect>
</property>
<property name="text">
<string>TextLabel</string>
</property>
</widget>
<widget class="QLabel" name="friendAvatarLabel">
<property name="geometry">
<rect>
<x>140</x>
<y>30</y>
<width>60</width>
<height>60</height>
</rect>
</property>
<property name="text">
<string>TextLabel</string>
</property>
</widget>
<widget class="QCheckBox" name="selectCheckBox">
<property name="geometry">
<rect>
<x>40</x>
<y>50</y>
<width>20</width>
<height>23</height>
</rect>
</property>
<property name="text">
<string/>
</property>
</widget>
</widget>
<resources/>
<connections/>
</ui>

View File

@ -0,0 +1,83 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>Form</class>
<widget class="QWidget" name="Form">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>400</width>
<height>220</height>
</rect>
</property>
<property name="minimumSize">
<size>
<width>400</width>
<height>220</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>400</width>
<height>220</height>
</size>
</property>
<property name="windowTitle">
<string>Form</string>
</property>
<widget class="QLabel" name="passwordLabel">
<property name="geometry">
<rect>
<x>10</x>
<y>20</y>
<width>380</width>
<height>20</height>
</rect>
</property>
<property name="text">
<string>TextLabel</string>
</property>
</widget>
<widget class="QPushButton" name="copyPasswordPushButton">
<property name="geometry">
<rect>
<x>10</x>
<y>60</y>
<width>380</width>
<height>40</height>
</rect>
</property>
<property name="text">
<string>PushButton</string>
</property>
</widget>
<widget class="QLabel" name="peerLimitLabel">
<property name="geometry">
<rect>
<x>10</x>
<y>120</y>
<width>380</width>
<height>20</height>
</rect>
</property>
<property name="text">
<string>TextLabel</string>
</property>
</widget>
<widget class="QLabel" name="privacyStateLabel">
<property name="geometry">
<rect>
<x>10</x>
<y>160</y>
<width>380</width>
<height>20</height>
</rect>
</property>
<property name="text">
<string>TextLabel</string>
</property>
</widget>
</widget>
<resources/>
<connections/>
</ui>

View File

@ -0,0 +1,113 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>Form</class>
<widget class="QWidget" name="Form">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>600</width>
<height>500</height>
</rect>
</property>
<property name="minimumSize">
<size>
<width>600</width>
<height>500</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>600</width>
<height>500</height>
</size>
</property>
<property name="windowTitle">
<string>Form</string>
</property>
<widget class="QLabel" name="noInvitesLabel">
<property name="geometry">
<rect>
<x>0</x>
<y>150</y>
<width>600</width>
<height>25</height>
</rect>
</property>
<property name="text">
<string>TextLabel</string>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
</widget>
<widget class="QListWidget" name="invitesListWidget">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>600</width>
<height>341</height>
</rect>
</property>
</widget>
<widget class="QLineEdit" name="nickLineEdit">
<property name="geometry">
<rect>
<x>10</x>
<y>360</y>
<width>350</width>
<height>35</height>
</rect>
</property>
</widget>
<widget class="QLineEdit" name="passwordLineEdit">
<property name="geometry">
<rect>
<x>10</x>
<y>410</y>
<width>350</width>
<height>35</height>
</rect>
</property>
</widget>
<widget class="QComboBox" name="statusComboBox">
<property name="geometry">
<rect>
<x>390</x>
<y>390</y>
<width>200</width>
<height>35</height>
</rect>
</property>
</widget>
<widget class="QPushButton" name="acceptPushButton">
<property name="geometry">
<rect>
<x>40</x>
<y>460</y>
<width>201</width>
<height>31</height>
</rect>
</property>
<property name="text">
<string>PushButton</string>
</property>
</widget>
<widget class="QPushButton" name="declinePushButton">
<property name="geometry">
<rect>
<x>360</x>
<y>460</y>
<width>201</width>
<height>31</height>
</rect>
</property>
<property name="text">
<string>PushButton</string>
</property>
</widget>
</widget>
<resources/>
<connections/>
</ui>

View File

@ -0,0 +1,123 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>Form</class>
<widget class="QWidget" name="Form">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>658</width>
<height>283</height>
</rect>
</property>
<property name="windowTitle">
<string>Form</string>
</property>
<widget class="QLineEdit" name="passwordLineEdit">
<property name="geometry">
<rect>
<x>180</x>
<y>20</y>
<width>450</width>
<height>41</height>
</rect>
</property>
</widget>
<widget class="QLabel" name="passwordLabel">
<property name="geometry">
<rect>
<x>20</x>
<y>30</y>
<width>145</width>
<height>20</height>
</rect>
</property>
<property name="text">
<string>TextLabel</string>
</property>
</widget>
<widget class="QLabel" name="peerLimitLabel">
<property name="geometry">
<rect>
<x>20</x>
<y>80</y>
<width>145</width>
<height>20</height>
</rect>
</property>
<property name="text">
<string>TextLabel</string>
</property>
</widget>
<widget class="QSpinBox" name="peersLimitSpinBox">
<property name="geometry">
<rect>
<x>180</x>
<y>70</y>
<width>450</width>
<height>40</height>
</rect>
</property>
<property name="minimum">
<number>2</number>
</property>
<property name="maximum">
<number>9999</number>
</property>
<property name="value">
<number>512</number>
</property>
</widget>
<widget class="QLabel" name="privacyStateLabel">
<property name="geometry">
<rect>
<x>20</x>
<y>130</y>
<width>145</width>
<height>20</height>
</rect>
</property>
<property name="text">
<string>TextLabel</string>
</property>
</widget>
<widget class="QComboBox" name="privacyStateComboBox">
<property name="geometry">
<rect>
<x>180</x>
<y>120</y>
<width>450</width>
<height>40</height>
</rect>
</property>
</widget>
<widget class="QPushButton" name="deletePushButton">
<property name="geometry">
<rect>
<x>20</x>
<y>180</y>
<width>300</width>
<height>41</height>
</rect>
</property>
<property name="text">
<string>PushButton</string>
</property>
</widget>
<widget class="QPushButton" name="savePushButton">
<property name="geometry">
<rect>
<x>20</x>
<y>220</y>
<width>611</width>
<height>41</height>
</rect>
</property>
<property name="text">
<string>PushButton</string>
</property>
</widget>
</widget>
<resources/>
<connections/>
</ui>

View File

@ -0,0 +1,255 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>Form</class>
<widget class="QWidget" name="Form">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>552</width>
<height>847</height>
</rect>
</property>
<property name="windowTitle">
<string>Form</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_2">
<item>
<widget class="QScrollArea" name="scrollArea">
<property name="verticalScrollBarPolicy">
<enum>Qt::ScrollBarAsNeeded</enum>
</property>
<property name="widgetResizable">
<bool>true</bool>
</property>
<widget class="QWidget" name="scrollAreaWidgetContents_3">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>532</width>
<height>827</height>
</rect>
</property>
<widget class="QLabel" name="themeLabel">
<property name="geometry">
<rect>
<x>30</x>
<y>140</y>
<width>67</width>
<height>17</height>
</rect>
</property>
<property name="text">
<string>TextLabel</string>
</property>
</widget>
<widget class="QComboBox" name="themeComboBox">
<property name="geometry">
<rect>
<x>20</x>
<y>180</y>
<width>471</width>
<height>31</height>
</rect>
</property>
</widget>
<widget class="QComboBox" name="languageComboBox">
<property name="geometry">
<rect>
<x>20</x>
<y>60</y>
<width>471</width>
<height>31</height>
</rect>
</property>
</widget>
<widget class="QLabel" name="languageLabel">
<property name="geometry">
<rect>
<x>30</x>
<y>20</y>
<width>67</width>
<height>17</height>
</rect>
</property>
<property name="text">
<string>TextLabel</string>
</property>
</widget>
<!--
<widget class="QCheckBox" name="mirrorModeCheckBox">
<property name="geometry">
<rect>
<x>30</x>
<y>220</y>
<width>461</width>
<height>23</height>
</rect>
</property>
<property name="text">
<string>CheckBox</string>
</property>
</widget>
-->
<widget class="QGroupBox" name="smileysGroupBox">
<property name="geometry">
<rect>
<x>30</x>
<y>280</y>
<width>461</width>
<height>221</height>
</rect>
</property>
<property name="title">
<string>GroupBox</string>
</property>
<widget class="QCheckBox" name="smileysCheckBox">
<property name="geometry">
<rect>
<x>30</x>
<y>40</y>
<width>92</width>
<height>23</height>
</rect>
</property>
<property name="text">
<string>CheckBox</string>
</property>
</widget>
<widget class="QLabel" name="smileysPackLabel">
<property name="geometry">
<rect>
<x>30</x>
<y>80</y>
<width>411</width>
<height>17</height>
</rect>
</property>
<property name="text">
<string>TextLabel</string>
</property>
</widget>
<widget class="QComboBox" name="smileysPackComboBox">
<property name="geometry">
<rect>
<x>30</x>
<y>120</y>
<width>411</width>
<height>31</height>
</rect>
</property>
</widget>
</widget>
<widget class="QCheckBox" name="compactModeCheckBox">
<property name="geometry">
<rect>
<x>30</x>
<y>250</y>
<width>461</width>
<height>23</height>
</rect>
</property>
<property name="text">
<string>CheckBox</string>
</property>
</widget>
<widget class="QPushButton" name="importStickersPushButton">
<property name="geometry">
<rect>
<x>30</x>
<y>750</y>
<width>471</width>
<height>40</height>
</rect>
</property>
<property name="text">
<string>PushButton</string>
</property>
</widget>
<widget class="QPushButton" name="importSmileysPushButton">
<property name="geometry">
<rect>
<x>30</x>
<y>690</y>
<width>471</width>
<height>40</height>
</rect>
</property>
<property name="text">
<string>PushButton</string>
</property>
</widget>
<widget class="QCheckBox" name="showAvatarsCheckBox">
<property name="geometry">
<rect>
<x>30</x>
<y>520</y>
<width>461</width>
<height>23</height>
</rect>
</property>
<property name="text">
<string>CheckBox</string>
</property>
</widget>
<widget class="QGroupBox" name="appClosingGroupBox">
<property name="geometry">
<rect>
<x>30</x>
<y>550</y>
<width>471</width>
<height>131</height>
</rect>
</property>
<property name="title">
<string>GroupBox</string>
</property>
<widget class="QRadioButton" name="closeRadioButton">
<property name="geometry">
<rect>
<x>30</x>
<y>30</y>
<width>421</width>
<height>23</height>
</rect>
</property>
<property name="text">
<string>RadioButton</string>
</property>
</widget>
<widget class="QRadioButton" name="hideRadioButton">
<property name="geometry">
<rect>
<x>30</x>
<y>60</y>
<width>431</width>
<height>23</height>
</rect>
</property>
<property name="text">
<string>RadioButton</string>
</property>
</widget>
<widget class="QRadioButton" name="closeToTrayRadioButton">
<property name="geometry">
<rect>
<x>30</x>
<y>90</y>
<width>421</width>
<height>23</height>
</rect>
</property>
<property name="text">
<string>RadioButton</string>
</property>
</widget>
</widget>
</widget>
</widget>
</item>
</layout>
</widget>
<resources/>
<connections/>
</ui>

View File

@ -0,0 +1,139 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>Form</class>
<widget class="QWidget" name="Form">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>740</width>
<height>320</height>
</rect>
</property>
<property name="minimumSize">
<size>
<width>740</width>
<height>320</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>740</width>
<height>320</height>
</size>
</property>
<property name="windowTitle">
<string>Form</string>
</property>
<widget class="QLabel" name="chatIdLabel">
<property name="geometry">
<rect>
<x>30</x>
<y>30</y>
<width>67</width>
<height>17</height>
</rect>
</property>
<property name="text">
<string>TextLabel</string>
</property>
</widget>
<widget class="QLabel" name="passwordLabel">
<property name="geometry">
<rect>
<x>30</x>
<y>90</y>
<width>67</width>
<height>17</height>
</rect>
</property>
<property name="text">
<string>TextLabel</string>
</property>
</widget>
<widget class="QPushButton" name="joinGroupButton">
<property name="enabled">
<bool>false</bool>
</property>
<property name="geometry">
<rect>
<x>30</x>
<y>260</y>
<width>680</width>
<height>51</height>
</rect>
</property>
<property name="text">
<string/>
</property>
</widget>
<widget class="QLineEdit" name="chatIdLineEdit">
<property name="geometry">
<rect>
<x>190</x>
<y>20</y>
<width>520</width>
<height>41</height>
</rect>
</property>
</widget>
<widget class="QLineEdit" name="passwordLineEdit">
<property name="geometry">
<rect>
<x>190</x>
<y>80</y>
<width>520</width>
<height>41</height>
</rect>
</property>
</widget>
<widget class="QLabel" name="nickLabel">
<property name="geometry">
<rect>
<x>30</x>
<y>150</y>
<width>67</width>
<height>17</height>
</rect>
</property>
<property name="text">
<string>TextLabel</string>
</property>
</widget>
<widget class="QLabel" name="statusLabel">
<property name="geometry">
<rect>
<x>30</x>
<y>210</y>
<width>67</width>
<height>17</height>
</rect>
</property>
<property name="text">
<string>TextLabel</string>
</property>
</widget>
<widget class="QLineEdit" name="nickLineEdit">
<property name="geometry">
<rect>
<x>190</x>
<y>140</y>
<width>520</width>
<height>41</height>
</rect>
</property>
</widget>
<widget class="QComboBox" name="statusComboBox">
<property name="geometry">
<rect>
<x>190</x>
<y>200</y>
<width>520</width>
<height>41</height>
</rect>
</property>
</widget>
</widget>
<resources/>
<connections/>
</ui>

View File

@ -0,0 +1,135 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>loginScreen</class>
<widget class="QWidget" name="loginScreen">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>400</width>
<height>200</height>
</rect>
</property>
<property name="minimumSize">
<size>
<width>400</width>
<height>200</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>400</width>
<height>200</height>
</size>
</property>
<property name="windowTitle">
<string>Form</string>
</property>
<widget class="QLabel" name="toxygenLabel">
<property name="geometry">
<rect>
<x>0</x>
<y>5</y>
<width>401</width>
<height>30</height>
</rect>
</property>
<property name="font">
<font>
<pointsize>16</pointsize>
<weight>75</weight>
<bold>true</bold>
</font>
</property>
<property name="text">
<string>Toxygen</string>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
</widget>
<widget class="QGroupBox" name="newProfileGroupBox">
<property name="geometry">
<rect>
<x>10</x>
<y>40</y>
<width>180</width>
<height>150</height>
</rect>
</property>
<property name="title">
<string>GroupBox</string>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
<widget class="QPushButton" name="createProfilePushButton">
<property name="geometry">
<rect>
<x>10</x>
<y>110</y>
<width>160</width>
<height>27</height>
</rect>
</property>
<property name="text">
<string>PushButton</string>
</property>
</widget>
</widget>
<widget class="QGroupBox" name="existingProfileGroupBox">
<property name="geometry">
<rect>
<x>210</x>
<y>40</y>
<width>180</width>
<height>150</height>
</rect>
</property>
<property name="title">
<string>GroupBox</string>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
<widget class="QComboBox" name="profilesComboBox">
<property name="geometry">
<rect>
<x>10</x>
<y>40</y>
<width>160</width>
<height>27</height>
</rect>
</property>
</widget>
<widget class="QCheckBox" name="defaultProfileCheckBox">
<property name="geometry">
<rect>
<x>10</x>
<y>75</y>
<width>160</width>
<height>27</height>
</rect>
</property>
<property name="text">
<string>CheckBox</string>
</property>
</widget>
<widget class="QPushButton" name="loadProfilePushButton">
<property name="geometry">
<rect>
<x>10</x>
<y>110</y>
<width>160</width>
<height>27</height>
</rect>
</property>
<property name="text">
<string>PushButton</string>
</property>
</widget>
</widget>
</widget>
<resources/>
<connections/>
</ui>

View File

@ -0,0 +1,94 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>Form</class>
<widget class="QWidget" name="Form">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>270</width>
<height>500</height>
</rect>
</property>
<property name="cursor">
<cursorShape>PointingHandCursor</cursorShape>
</property>
<property name="windowTitle">
<string>Form</string>
</property>
<widget class="QLabel" name="avatarLabel">
<property name="geometry">
<rect>
<x>5</x>
<y>5</y>
<width>64</width>
<height>64</height>
</rect>
</property>
<property name="cursor">
<cursorShape>PointingHandCursor</cursorShape>
</property>
<property name="text">
<string>TextLabel</string>
</property>
</widget>
<widget class="QLineEdit" name="searchLineEdit">
<property name="geometry">
<rect>
<x>0</x>
<y>75</y>
<width>150</width>
<height>25</height>
</rect>
</property>
</widget>
<widget class="QComboBox" name="contactsFilterComboBox">
<property name="geometry">
<rect>
<x>150</x>
<y>75</y>
<width>120</width>
<height>25</height>
</rect>
</property>
</widget>
<widget class="QLabel" name="searchLabel">
<property name="geometry">
<rect>
<x>0</x>
<y>77</y>
<width>20</width>
<height>20</height>
</rect>
</property>
<property name="text">
<string>TextLabel</string>
</property>
</widget>
<widget class="QListWidget" name="friendsListWidget">
<property name="geometry">
<rect>
<x>0</x>
<y>100</y>
<width>270</width>
<height>400</height>
</rect>
</property>
</widget>
<widget class="QPushButton" name="groupInvitesPushButton">
<property name="geometry">
<rect>
<x>0</x>
<y>100</y>
<width>270</width>
<height>30</height>
</rect>
</property>
<property name="text">
<string>PushButton</string>
</property>
</widget>
</widget>
<resources/>
<connections/>
</ui>

View File

@ -0,0 +1,196 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>Form</class>
<widget class="QWidget" name="Form">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>400</width>
<height>545</height>
</rect>
</property>
<property name="minimumSize">
<size>
<width>400</width>
<height>545</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>400</width>
<height>545</height>
</size>
</property>
<property name="windowTitle">
<string>Form</string>
</property>
<widget class="QCheckBox" name="ipv6CheckBox">
<property name="geometry">
<rect>
<x>30</x>
<y>20</y>
<width>150</width>
<height>30</height>
</rect>
</property>
<property name="text">
<string>CheckBox</string>
</property>
</widget>
<widget class="QCheckBox" name="udpCheckBox">
<property name="geometry">
<rect>
<x>210</x>
<y>20</y>
<width>150</width>
<height>30</height>
</rect>
</property>
<property name="text">
<string>CheckBox</string>
</property>
</widget>
<widget class="QCheckBox" name="proxyCheckBox">
<property name="geometry">
<rect>
<x>30</x>
<y>140</y>
<width>150</width>
<height>30</height>
</rect>
</property>
<property name="text">
<string>CheckBox</string>
</property>
</widget>
<widget class="QRadioButton" name="httpProxyRadioButton">
<property name="geometry">
<rect>
<x>30</x>
<y>190</y>
<width>150</width>
<height>25</height>
</rect>
</property>
<property name="text">
<string>RadioButton</string>
</property>
</widget>
<widget class="QRadioButton" name="socksProxyRadioButton">
<property name="geometry">
<rect>
<x>30</x>
<y>230</y>
<width>150</width>
<height>25</height>
</rect>
</property>
<property name="text">
<string>RadioButton</string>
</property>
</widget>
<widget class="QCheckBox" name="lanCheckBox">
<property name="geometry">
<rect>
<x>30</x>
<y>100</y>
<width>150</width>
<height>30</height>
</rect>
</property>
<property name="text">
<string>CheckBox</string>
</property>
</widget>
<widget class="QLabel" name="ipLabel">
<property name="geometry">
<rect>
<x>30</x>
<y>280</y>
<width>60</width>
<height>20</height>
</rect>
</property>
<property name="text">
<string>TextLabel</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
</widget>
<widget class="QLabel" name="portLabel">
<property name="geometry">
<rect>
<x>30</x>
<y>330</y>
<width>60</width>
<height>20</height>
</rect>
</property>
<property name="text">
<string>TextLabel</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
</widget>
<widget class="QLabel" name="urlLabel">
<property name="geometry">
<rect>
<x>30</x>
<y>380</y>
<width>60</width>
<height>20</height>
</rect>
</property>
<property name="text">
<string>Chat Url</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
</widget>
<widget class="QPushButton" name="restartCorePushButton">
<property name="geometry">
<rect>
<x>30</x>
<y>430</y>
<width>340</width>
<height>40</height>
</rect>
</property>
<property name="text">
<string>PushButton</string>
</property>
</widget>
<widget class="QCheckBox" name="downloadNodesCheckBox">
<property name="geometry">
<rect>
<x>30</x>
<y>60</y>
<width>340</width>
<height>30</height>
</rect>
</property>
<property name="text">
<string>CheckBox</string>
</property>
</widget>
<widget class="QLabel" name="warningLabel">
<property name="geometry">
<rect>
<x>30</x>
<y>480</y>
<width>340</width>
<height>65</height>
</rect>
</property>
<property name="text">
<string>TextLabel</string>
</property>
</widget>
</widget>
<resources/>
<connections/>
</ui>

View File

@ -0,0 +1,71 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>Form</class>
<widget class="QWidget" name="Form">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>320</width>
<height>201</height>
</rect>
</property>
<property name="windowTitle">
<string>Form</string>
</property>
<widget class="QCheckBox" name="notificationsCheckBox">
<property name="geometry">
<rect>
<x>20</x>
<y>20</y>
<width>271</width>
<height>41</height>
</rect>
</property>
<property name="text">
<string>CheckBox</string>
</property>
</widget>
<widget class="QCheckBox" name="soundNotificationsCheckBox">
<property name="geometry">
<rect>
<x>20</x>
<y>60</y>
<width>271</width>
<height>41</height>
</rect>
</property>
<property name="text">
<string>CheckBox</string>
</property>
</widget>
<widget class="QCheckBox" name="groupNotificationsCheckBox">
<property name="geometry">
<rect>
<x>20</x>
<y>100</y>
<width>271</width>
<height>41</height>
</rect>
</property>
<property name="text">
<string>CheckBox</string>
</property>
</widget>
<widget class="QCheckBox" name="callsSoundCheckBox">
<property name="geometry">
<rect>
<x>20</x>
<y>140</y>
<width>271</width>
<height>41</height>
</rect>
</property>
<property name="text">
<string>CheckBox</string>
</property>
</widget>
</widget>
<resources/>
<connections/>
</ui>

View File

@ -0,0 +1,202 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>Form</class>
<widget class="QWidget" name="Form">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>600</width>
<height>500</height>
</rect>
</property>
<property name="minimumSize">
<size>
<width>600</width>
<height>500</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>600</width>
<height>500</height>
</size>
</property>
<property name="windowTitle">
<string>Form</string>
</property>
<widget class="QLabel" name="peerNameLabel">
<property name="geometry">
<rect>
<x>110</x>
<y>10</y>
<width>431</width>
<height>40</height>
</rect>
</property>
<property name="text">
<string>TextLabel</string>
</property>
</widget>
<widget class="QPushButton" name="sendPrivateMessagePushButton">
<property name="geometry">
<rect>
<x>50</x>
<y>140</y>
<width>500</width>
<height>50</height>
</rect>
</property>
<property name="text">
<string>PushButton</string>
</property>
</widget>
<widget class="QCheckBox" name="ignorePeerCheckBox">
<property name="geometry">
<rect>
<x>50</x>
<y>100</y>
<width>500</width>
<height>23</height>
</rect>
</property>
<property name="text">
<string>CheckBox</string>
</property>
</widget>
<widget class="QGroupBox" name="banGroupBox">
<property name="geometry">
<rect>
<x>50</x>
<y>300</y>
<width>500</width>
<height>161</height>
</rect>
</property>
<property name="title">
<string>GroupBox</string>
</property>
<!--
<widget class="QPushButton" name="banPushButton">
<property name="geometry">
<rect>
<x>380</x>
<y>50</y>
<width>101</width>
<height>41</height>
</rect>
</property>
<property name="text">
<string>PushButton</string>
</property>
</widget>
-->
<widget class="QRadioButton" name="ipBanRadioButton">
<property name="geometry">
<rect>
<x>40</x>
<y>40</y>
<width>251</width>
<height>23</height>
</rect>
</property>
<property name="text">
<string>RadioButton</string>
</property>
<property name="checked">
<bool>true</bool>
</property>
</widget>
<widget class="QRadioButton" name="nickBanRadioButton">
<property name="geometry">
<rect>
<x>40</x>
<y>80</y>
<width>251</width>
<height>23</height>
</rect>
</property>
<property name="text">
<string>RadioButton</string>
</property>
</widget>
<widget class="QRadioButton" name="pkBanRadioButton">
<property name="geometry">
<rect>
<x>40</x>
<y>120</y>
<width>251</width>
<height>23</height>
</rect>
</property>
<property name="text">
<string>RadioButton</string>
</property>
</widget>
<widget class="QPushButton" name="kickPushButton">
<property name="geometry">
<rect>
<x>380</x>
<y>100</y>
<width>101</width>
<height>41</height>
</rect>
</property>
<property name="text">
<string>PushButton</string>
</property>
</widget>
</widget>
<widget class="QLabel" name="roleLabel">
<property name="geometry">
<rect>
<x>50</x>
<y>60</y>
<width>67</width>
<height>20</height>
</rect>
</property>
<property name="text">
<string>TextLabel</string>
</property>
</widget>
<widget class="QLabel" name="roleNameLabel">
<property name="geometry">
<rect>
<x>130</x>
<y>60</y>
<width>411</width>
<height>20</height>
</rect>
</property>
<property name="text">
<string>TextLabel</string>
</property>
</widget>
<widget class="QPushButton" name="copyPublicKeyPushButton">
<property name="geometry">
<rect>
<x>50</x>
<y>210</y>
<width>500</width>
<height>50</height>
</rect>
</property>
<property name="text">
<string>PushButton</string>
</property>
</widget>
<widget class="QComboBox" name="rolesComboBox">
<property name="geometry">
<rect>
<x>130</x>
<y>55</y>
<width>291</width>
<height>30</height>
</rect>
</property>
</widget>
</widget>
<resources/>
<connections/>
</ui>

View File

@ -0,0 +1,280 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>Form</class>
<widget class="QWidget" name="Form">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>900</width>
<height>680</height>
</rect>
</property>
<property name="windowTitle">
<string>Form</string>
</property>
<widget class="QLabel" name="nameLabel">
<property name="geometry">
<rect>
<x>30</x>
<y>10</y>
<width>161</width>
<height>30</height>
</rect>
</property>
<property name="text">
<string>TextLabel</string>
</property>
</widget>
<widget class="QLabel" name="statusLabel">
<property name="geometry">
<rect>
<x>30</x>
<y>90</y>
<width>161</width>
<height>30</height>
</rect>
</property>
<property name="text">
<string>TextLabel</string>
</property>
</widget>
<widget class="QLineEdit" name="nameLineEdit">
<property name="geometry">
<rect>
<x>30</x>
<y>50</y>
<width>421</width>
<height>30</height>
</rect>
</property>
</widget>
<widget class="QLineEdit" name="statusMessageLineEdit">
<property name="geometry">
<rect>
<x>30</x>
<y>130</y>
<width>421</width>
<height>30</height>
</rect>
</property>
</widget>
<widget class="QComboBox" name="statusComboBox">
<property name="geometry">
<rect>
<x>520</x>
<y>30</y>
<width>311</width>
<height>30</height>
</rect>
</property>
</widget>
<widget class="QLabel" name="toxIdTitleLabel">
<property name="geometry">
<rect>
<x>40</x>
<y>180</y>
<width>131</width>
<height>20</height>
</rect>
</property>
<property name="text">
<string>TextLabel</string>
</property>
</widget>
<widget class="QLabel" name="toxIdLabel">
<property name="geometry">
<rect>
<x>40</x>
<y>210</y>
<width>831</width>
<height>60</height>
</rect>
</property>
<property name="text">
<string>TextLabel</string>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
<widget class="QPushButton" name="copyToxIdPushButton">
<property name="geometry">
<rect>
<x>40</x>
<y>280</y>
<width>371</width>
<height>30</height>
</rect>
</property>
<property name="text">
<string>PushButton</string>
</property>
</widget>
<widget class="QPushButton" name="copyPublicKeyPushButton">
<property name="geometry">
<rect>
<x>440</x>
<y>280</y>
<width>371</width>
<height>30</height>
</rect>
</property>
<property name="text">
<string>PushButton</string>
</property>
</widget>
<widget class="QPushButton" name="newAvatarPushButton">
<property name="geometry">
<rect>
<x>520</x>
<y>80</y>
<width>321</width>
<height>34</height>
</rect>
</property>
<property name="text">
<string>PushButton</string>
</property>
</widget>
<widget class="QPushButton" name="resetAvatarPushButton">
<property name="geometry">
<rect>
<x>520</x>
<y>130</y>
<width>321</width>
<height>34</height>
</rect>
</property>
<property name="text">
<string>PushButton</string>
</property>
</widget>
<widget class="QLabel" name="profilePasswordLabel">
<property name="geometry">
<rect>
<x>60</x>
<y>380</y>
<width>161</width>
<height>30</height>
</rect>
</property>
<property name="text">
<string>TextLabel</string>
</property>
</widget>
<widget class="QLineEdit" name="passwordLineEdit">
<property name="geometry">
<rect>
<x>50</x>
<y>420</y>
<width>421</width>
<height>30</height>
</rect>
</property>
</widget>
<widget class="QLineEdit" name="confirmPasswordLineEdit">
<property name="geometry">
<rect>
<x>50</x>
<y>470</y>
<width>421</width>
<height>30</height>
</rect>
</property>
</widget>
<widget class="QLabel" name="emptyPasswordLabel">
<property name="geometry">
<rect>
<x>500</x>
<y>420</y>
<width>381</width>
<height>20</height>
</rect>
</property>
<property name="text">
<string>TextLabel</string>
</property>
</widget>
<widget class="QLabel" name="warningLabel">
<property name="geometry">
<rect>
<x>60</x>
<y>580</y>
<width>381</width>
<height>20</height>
</rect>
</property>
<property name="text">
<string>TextLabel</string>
</property>
</widget>
<widget class="QPushButton" name="defaultProfilePushButton">
<property name="geometry">
<rect>
<x>40</x>
<y>630</y>
<width>831</width>
<height>34</height>
</rect>
</property>
<property name="text">
<string>PushButton</string>
</property>
</widget>
<widget class="QPushButton" name="changePasswordPushButton">
<property name="geometry">
<rect>
<x>50</x>
<y>520</y>
<width>421</width>
<height>34</height>
</rect>
</property>
<property name="text">
<string>PushButton</string>
</property>
</widget>
<widget class="QLabel" name="invalidPasswordsLabel">
<property name="geometry">
<rect>
<x>500</x>
<y>470</y>
<width>381</width>
<height>20</height>
</rect>
</property>
<property name="text">
<string>TextLabel</string>
</property>
</widget>
<widget class="QPushButton" name="exportProfilePushButton">
<property name="geometry">
<rect>
<x>40</x>
<y>330</y>
<width>371</width>
<height>34</height>
</rect>
</property>
<property name="text">
<string>PushButton</string>
</property>
</widget>
<widget class="QPushButton" name="newNoSpamPushButton">
<property name="geometry">
<rect>
<x>440</x>
<y>330</y>
<width>371</width>
<height>34</height>
</rect>
</property>
<property name="text">
<string>PushButton</string>
</property>
</widget>
</widget>
<resources/>
<connections/>
</ui>

View File

@ -0,0 +1,119 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>Form</class>
<widget class="QWidget" name="Form">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>600</width>
<height>500</height>
</rect>
</property>
<property name="minimumSize">
<size>
<width>600</width>
<height>500</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>600</width>
<height>500</height>
</size>
</property>
<property name="windowTitle">
<string>Form</string>
</property>
<widget class="QLabel" name="statusLabel">
<property name="geometry">
<rect>
<x>50</x>
<y>120</y>
<width>67</width>
<height>20</height>
</rect>
</property>
<property name="text">
<string>TextLabel</string>
</property>
</widget>
<widget class="QPushButton" name="copyPublicKeyPushButton">
<property name="geometry">
<rect>
<x>50</x>
<y>250</y>
<width>500</width>
<height>50</height>
</rect>
</property>
<property name="text">
<string>PushButton</string>
</property>
</widget>
<widget class="QComboBox" name="statusComboBox">
<property name="geometry">
<rect>
<x>140</x>
<y>110</y>
<width>400</width>
<height>40</height>
</rect>
</property>
</widget>
<widget class="QLabel" name="nameLabel">
<property name="geometry">
<rect>
<x>50</x>
<y>40</y>
<width>67</width>
<height>20</height>
</rect>
</property>
<property name="text">
<string>TextLabel</string>
</property>
</widget>
<widget class="QLabel" name="roleLabel">
<property name="geometry">
<rect>
<x>50</x>
<y>190</y>
<width>67</width>
<height>20</height>
</rect>
</property>
<property name="text">
<string>TextLabel</string>
</property>
</widget>
<widget class="QLabel" name="roleNameLabel">
<property name="geometry">
<rect>
<x>140</x>
<y>190</y>
<width>411</width>
<height>20</height>
</rect>
</property>
<property name="text">
<string>TextLabel</string>
</property>
</widget>
<widget class="QPushButton" name="savePushButton">
<property name="geometry">
<rect>
<x>50</x>
<y>330</y>
<width>500</width>
<height>50</height>
</rect>
</property>
<property name="text">
<string>PushButton</string>
</property>
</widget>
</widget>
<resources/>
<connections/>
</ui>

View File

@ -0,0 +1,67 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>Form</class>
<widget class="QWidget" name="Form">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>400</width>
<height>120</height>
</rect>
</property>
<property name="minimumSize">
<size>
<width>400</width>
<height>120</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>400</width>
<height>120</height>
</size>
</property>
<property name="windowTitle">
<string>Form</string>
</property>
<widget class="QLabel" name="updateModeLabel">
<property name="geometry">
<rect>
<x>25</x>
<y>5</y>
<width>350</width>
<height>20</height>
</rect>
</property>
<property name="text">
<string>TextLabel</string>
</property>
</widget>
<widget class="QComboBox" name="updateModeComboBox">
<property name="geometry">
<rect>
<x>25</x>
<y>30</y>
<width>350</width>
<height>30</height>
</rect>
</property>
</widget>
<widget class="QPushButton" name="updatePushButton">
<property name="geometry">
<rect>
<x>25</x>
<y>70</y>
<width>350</width>
<height>30</height>
</rect>
</property>
<property name="text">
<string>PushButton</string>
</property>
</widget>
</widget>
<resources/>
<connections/>
</ui>

View File

@ -0,0 +1,77 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>Form</class>
<widget class="QWidget" name="Form">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>400</width>
<height>120</height>
</rect>
</property>
<property name="minimumSize">
<size>
<width>400</width>
<height>120</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>400</width>
<height>120</height>
</size>
</property>
<property name="windowTitle">
<string>Form</string>
</property>
<widget class="QLabel" name="deviceLabel">
<property name="geometry">
<rect>
<x>25</x>
<y>5</y>
<width>350</width>
<height>20</height>
</rect>
</property>
<property name="text">
<string>TextLabel</string>
</property>
</widget>
<widget class="QComboBox" name="deviceComboBox">
<property name="geometry">
<rect>
<x>25</x>
<y>30</y>
<width>350</width>
<height>30</height>
</rect>
</property>
</widget>
<widget class="QPushButton" name="selectRegionPushButton">
<property name="geometry">
<rect>
<x>25</x>
<y>70</y>
<width>350</width>
<height>30</height>
</rect>
</property>
<property name="text">
<string>PushButton</string>
</property>
</widget>
<widget class="QComboBox" name="resolutionComboBox">
<property name="geometry">
<rect>
<x>25</x>
<y>70</y>
<width>350</width>
<height>30</height>
</rect>
</property>
</widget>
</widget>
<resources/>
<connections/>
</ui>

View File

@ -33,18 +33,14 @@ class ProfileManager:
if not os.path.exists(avatars_directory):
os.makedirs(avatars_directory)
# -----------------------------------------------------------------------------------------------------------------
# Properties
# -----------------------------------------------------------------------------------------------------------------
def get_profile_saved_event(self):
return self._profile_saved_event
profile_saved_event = property(get_profile_saved_event)
# -----------------------------------------------------------------------------------------------------------------
# Public methods
# -----------------------------------------------------------------------------------------------------------------
def open_profile(self):
with open(self._path, 'rb') as fl:

View File

@ -192,18 +192,14 @@ class Settings(dict):
self.unlockScreen = False
# -----------------------------------------------------------------------------------------------------------------
# Properties
# -----------------------------------------------------------------------------------------------------------------
def get_settings_saved_event(self):
return self._settings_saved_event
settings_saved_event = property(get_settings_saved_event)
# -----------------------------------------------------------------------------------------------------------------
# Public methods
# -----------------------------------------------------------------------------------------------------------------
def save(self):
text = json.dumps(self)
@ -252,9 +248,7 @@ class Settings(dict):
self._path = new_path
self.save()
# -----------------------------------------------------------------------------------------------------------------
# Static methods
# -----------------------------------------------------------------------------------------------------------------
@staticmethod
def get_auto_profile():
@ -387,9 +381,7 @@ class Settings(dict):
}
return retval
# -----------------------------------------------------------------------------------------------------------------
# Private methods
# -----------------------------------------------------------------------------------------------------------------
def _upgrade(self):
default = Settings.get_default_settings()

View File

@ -1,5 +0,0 @@
# -*- mode: python; indent-tabs-mode: nil; py-indent-offset: 4; coding: utf-8 -*-
# You need a libs directory beside this directory
# and you need to link your libtoxcore.so and libtoxav.so
# and libtoxencryptsave.so into ../libs/
# Link all 3 to libtoxcore.so if you have only libtoxcore.so

View File

@ -1,87 +0,0 @@
# -*- mode: python; indent-tabs-mode: nil; py-indent-offset: 4; coding: utf-8 -*-
import os
import sys
from ctypes import CDLL
# You need a libs directory beside this directory
# and you need to link your libtoxcore.so and libtoxav.so
# and libtoxencryptsave.so into ../libs/
# Link all 3 to libtoxcore.so if you have only libtoxcore.so
try:
import utils.util as util
sLIBS_DIR = util.get_libs_directory()
except ImportError:
sLIBS_DIR = os.path.join(os.path.dirname(os.path.dirname(__file__)),
'libs')
# environment variable TOXCORE_LIBS overrides
d = os.environ.get('TOXCORE_LIBS', '')
if d and os.path.exists(d):
sLIBS_DIR = d
if os.environ.get('DEBUG', ''):
print ('DBUG: Setting TOXCORE_LIBS to ' +d)
del d
class LibToxCore:
def __init__(self):
platform = sys.platform
if platform == 'win32':
libtoxcore = 'libtox.dll'
elif platform == 'darwin':
libtoxcore = 'libtoxcore.dylib'
else:
libtoxcore = 'libtoxcore.so'
# libtoxcore and libsodium may be installed in your os
# give libs/ precedence
libFile = os.path.join(sLIBS_DIR, libtoxcore)
if os.path.isfile(libFile):
self._libtoxcore = CDLL(libFile)
else:
self._libtoxcore = CDLL(libtoxcore)
def __getattr__(self, item):
return self._libtoxcore.__getattr__(item)
class LibToxAV:
def __init__(self):
platform = sys.platform
if platform == 'win32':
# on Windows av api is in libtox.dll
self._libtoxav = CDLL(os.path.join(sLIBS_DIR, 'libtox.dll'))
elif platform == 'darwin':
self._libtoxav = CDLL('libtoxcore.dylib')
else:
libFile = os.path.join(sLIBS_DIR, 'libtoxav.so')
if os.path.isfile(libFile):
self._libtoxav = CDLL(libFile)
else:
self._libtoxav = CDLL('libtoxav.so')
def __getattr__(self, item):
return self._libtoxav.__getattr__(item)
# figure out how to see if we have a combined library
class LibToxEncryptSave:
def __init__(self):
platform = sys.platform
if platform == 'win32':
# on Windows profile encryption api is in libtox.dll
self._lib_tox_encrypt_save = CDLL(os.path.join(sLIBS_DIR, 'libtox.dll'))
elif platform == 'darwin':
self._lib_tox_encrypt_save = CDLL('libtoxcore.dylib')
else:
libFile = os.path.join(sLIBS_DIR, 'libtoxencryptsave.so')
if os.path.isfile(libFile):
self._lib_tox_encrypt_save = CDLL(libFile)
else:
self._lib_tox_encrypt_save = CDLL('libtoxencryptsave.so')
def __getattr__(self, item):
return self._lib_tox_encrypt_save.__getattr__(item)
# figure out how to see if we have a combined library

View File

@ -1,80 +0,0 @@
tox_version_major
tox_version_minor
tox_version_patch
tox_version_is_compatible
tox_public_key_size
tox_secret_key_size
tox_conference_uid_size
tox_conference_id_size
tox_nospam_size
tox_address_size
tox_max_name_length
tox_max_status_message_length
tox_max_friend_request_length
tox_max_message_length
tox_max_custom_packet_size
tox_hash_length
tox_file_id_length
tox_max_filename_length
tox_max_hostname_length
tox_options_get_ipv6_enabled
tox_options_get_udp_enabled
tox_options_get_local_discovery_enabled
tox_options_get_dht_announcements_enabled
tox_options_get_proxy_type
tox_options_get_proxy_port
tox_options_get_start_port
tox_options_get_end_port
tox_options_get_tcp_port
tox_options_get_hole_punching_enabled
tox_options_get_savedata_type
tox_options_get_savedata_length
tox_options_get_experimental_thread_safety
tox_file_seek
tox_callback_conference_connected
tox_callback_conference_message
tox_callback_conference_title
tox_callback_conference_peer_list_changed
tox_conference_new
tox_conference_delete
tox_conference_peer_count
tox_conference_peer_get_name_size
tox_conference_peer_get_name
tox_conference_peer_get_public_key
tox_conference_peer_number_is_ours
tox_conference_offline_peer_count
tox_conference_offline_peer_get_name_size
tox_conference_offline_peer_get_name
tox_conference_offline_peer_get_public_key
tox_conference_offline_peer_get_last_active
tox_conference_set_max_offline
tox_conference_invite
tox_conference_join
tox_conference_send_message
tox_conference_get_title_size
tox_conference_get_title
tox_conference_set_title
tox_conference_get_chatlist_size
tox_conference_get_chatlist
tox_conference_get_type
tox_conference_get_id
tox_conference_by_id
tox_conference_get_uid
tox_conference_by_uid
tox_group_max_topic_length
tox_group_max_part_length
tox_group_max_group_name_length
tox_group_max_password_size
tox_group_chat_id_size
tox_group_peer_public_key_size
tox_group_peer_get_connection_status
tox_group_get_voice_state
tox_callback_group_voice_state
tox_group_get_topic_lock
tox_callback_group_topic_lock
tox_group_send_custom_private_packet
tox_callback_group_custom_private_packet
tox_group_founder_set_topic_lock
tox_group_founder_set_voice_state
tox_group_set_ignore
tox_group_mod_kick_peer

File diff suppressed because it is too large Load Diff

View File

@ -1,403 +0,0 @@
# -*- mode: python; indent-tabs-mode: nil; py-indent-offset: 4; coding: utf-8 -*-
from ctypes import (CFUNCTYPE, POINTER, ArgumentError, byref, c_bool, c_char_p,
c_int, c_int32, c_size_t, c_uint8, c_uint16, c_uint32,
c_void_p, cast)
from wrapper.libtox import LibToxAV
from wrapper.toxav_enums import *
def LOG_ERROR(a): print('EROR> '+a)
def LOG_WARN(a): print('WARN> '+a)
def LOG_INFO(a): print('INFO> '+a)
def LOG_DEBUG(a): print('DBUG> '+a)
def LOG_TRACE(a): pass # print('DEBUGx: '+a)
class ToxAV:
"""
The ToxAV instance type. Each ToxAV instance can be bound to only one Tox instance, and Tox instance can have only
one ToxAV instance. One must make sure to close ToxAV instance prior closing Tox instance otherwise undefined
behaviour occurs. Upon closing of ToxAV instance, all active calls will be forcibly terminated without notifying
peers.
"""
# -----------------------------------------------------------------------------------------------------------------
# Creation and destruction
# -----------------------------------------------------------------------------------------------------------------
def __init__(self, tox_pointer):
"""
Start new A/V session. There can only be only one session per Tox instance.
:param tox_pointer: pointer to Tox instance
"""
self.libtoxav = LibToxAV()
toxav_err_new = c_int()
f = self.libtoxav.toxav_new
f.restype = POINTER(c_void_p)
self._toxav_pointer = f(tox_pointer, byref(toxav_err_new))
toxav_err_new = toxav_err_new.value
if toxav_err_new == TOXAV_ERR_NEW['NULL']:
raise ArgumentError('One of the arguments to the function was NULL when it was not expected.')
elif toxav_err_new == TOXAV_ERR_NEW['MALLOC']:
raise MemoryError('Memory allocation failure while trying to allocate structures required for the A/V '
'session.')
elif toxav_err_new == TOXAV_ERR_NEW['MULTIPLE']:
raise RuntimeError('Attempted to create a second session for the same Tox instance.')
self.call_state_cb = None
self.audio_receive_frame_cb = None
self.video_receive_frame_cb = None
self.call_cb = None
def kill(self):
"""
Releases all resources associated with the A/V session.
If any calls were ongoing, these will be forcibly terminated without notifying peers. After calling this
function, no other functions may be called and the av pointer becomes invalid.
"""
self.libtoxav.toxav_kill(self._toxav_pointer)
def get_tox_pointer(self):
"""
Returns the Tox instance the A/V object was created for.
:return: pointer to the Tox instance
"""
self.libtoxav.toxav_get_tox.restype = POINTER(c_void_p)
return self.libtoxav.toxav_get_tox(self._toxav_pointer)
# -----------------------------------------------------------------------------------------------------------------
# A/V event loop
# -----------------------------------------------------------------------------------------------------------------
def iteration_interval(self):
"""
Returns the interval in milliseconds when the next toxav_iterate call should be. If no call is active at the
moment, this function returns 200.
:return: interval in milliseconds
"""
return self.libtoxav.toxav_iteration_interval(self._toxav_pointer)
def iterate(self):
"""
Main loop for the session. This function needs to be called in intervals of toxav_iteration_interval()
milliseconds. It is best called in the separate thread from tox_iterate.
"""
self.libtoxav.toxav_iterate(self._toxav_pointer)
# -----------------------------------------------------------------------------------------------------------------
# Call setup
# -----------------------------------------------------------------------------------------------------------------
def call(self, friend_number, audio_bit_rate, video_bit_rate):
"""
Call a friend. This will start ringing the friend.
It is the client's responsibility to stop ringing after a certain timeout, if such behaviour is desired. If the
client does not stop ringing, the library will not stop until the friend is disconnected. Audio and video
receiving are both enabled by default.
:param friend_number: The friend number of the friend that should be called.
:param audio_bit_rate: Audio bit rate in Kb/sec. Set this to 0 to disable audio sending.
:param video_bit_rate: Video bit rate in Kb/sec. Set this to 0 to disable video sending.
:return: True on success.
"""
toxav_err_call = c_int()
LOG_DEBUG(f"toxav_call")
result = self.libtoxav.toxav_call(self._toxav_pointer, c_uint32(friend_number), c_uint32(audio_bit_rate),
c_uint32(video_bit_rate), byref(toxav_err_call))
toxav_err_call = toxav_err_call.value
if toxav_err_call == TOXAV_ERR_CALL['OK']:
return bool(result)
elif toxav_err_call == TOXAV_ERR_CALL['MALLOC']:
raise MemoryError('A resource allocation error occurred while trying to create the structures required for '
'the call.')
elif toxav_err_call == TOXAV_ERR_CALL['SYNC']:
raise RuntimeError('Synchronization error occurred.')
elif toxav_err_call == TOXAV_ERR_CALL['FRIEND_NOT_FOUND']:
raise ArgumentError('The friend number did not designate a valid friend.')
elif toxav_err_call == TOXAV_ERR_CALL['FRIEND_NOT_CONNECTED']:
raise ArgumentError('The friend was valid, but not currently connected.')
elif toxav_err_call == TOXAV_ERR_CALL['FRIEND_ALREADY_IN_CALL']:
raise ArgumentError('Attempted to call a friend while already in an audio or video call with them.')
elif toxav_err_call == TOXAV_ERR_CALL['INVALID_BIT_RATE']:
raise ArgumentError('Audio or video bit rate is invalid.')
def callback_call(self, callback, user_data):
"""
Set the callback for the `call` event. Pass None to unset.
:param callback: The function for the call callback.
Should take pointer (c_void_p) to ToxAV object,
The friend number (c_uint32) from which the call is incoming.
True (c_bool) if friend is sending audio.
True (c_bool) if friend is sending video.
pointer (c_void_p) to user_data
:param user_data: pointer (c_void_p) to user data
"""
if callback is None:
self.libtoxav.toxav_callback_call(self._toxav_pointer, POINTER(None)(), user_data)
self.call_cb = None
return
LOG_DEBUG(f"toxav_callback_call")
c_callback = CFUNCTYPE(None, c_void_p, c_uint32, c_bool, c_bool, c_void_p)
self.call_cb = c_callback(callback)
self.libtoxav.toxav_callback_call(self._toxav_pointer, self.call_cb, user_data)
def answer(self, friend_number, audio_bit_rate, video_bit_rate):
"""
Accept an incoming call.
If answering fails for any reason, the call will still be pending and it is possible to try and answer it later.
Audio and video receiving are both enabled by default.
:param friend_number: The friend number of the friend that is calling.
:param audio_bit_rate: Audio bit rate in Kb/sec. Set this to 0 to disable audio sending.
:param video_bit_rate: Video bit rate in Kb/sec. Set this to 0 to disable video sending.
:return: True on success.
"""
toxav_err_answer = c_int()
LOG_DEBUG(f"toxav_answer")
result = self.libtoxav.toxav_answer(self._toxav_pointer, c_uint32(friend_number), c_uint32(audio_bit_rate),
c_uint32(video_bit_rate), byref(toxav_err_answer))
toxav_err_answer = toxav_err_answer.value
if toxav_err_answer == TOXAV_ERR_ANSWER['OK']:
return bool(result)
elif toxav_err_answer == TOXAV_ERR_ANSWER['SYNC']:
raise RuntimeError('Synchronization error occurred.')
elif toxav_err_answer == TOXAV_ERR_ANSWER['CODEC_INITIALIZATION']:
raise RuntimeError('Failed to initialize codecs for call session. Note that codec initiation will fail if '
'there is no receive callback registered for either audio or video.')
elif toxav_err_answer == TOXAV_ERR_ANSWER['FRIEND_NOT_FOUND']:
raise ArgumentError('The friend number did not designate a valid friend.')
elif toxav_err_answer == TOXAV_ERR_ANSWER['FRIEND_NOT_CALLING']:
raise ArgumentError('The friend was valid, but they are not currently trying to initiate a call. This is '
'also returned if this client is already in a call with the friend.')
elif toxav_err_answer == TOXAV_ERR_ANSWER['INVALID_BIT_RATE']:
raise ArgumentError('Audio or video bit rate is invalid.')
# -----------------------------------------------------------------------------------------------------------------
# Call state graph
# -----------------------------------------------------------------------------------------------------------------
def callback_call_state(self, callback, user_data):
"""
Set the callback for the `call_state` event. Pass None to unset.
:param callback: Python function.
The function for the call_state callback.
Should take pointer (c_void_p) to ToxAV object,
The friend number (c_uint32) for which the call state changed.
The bitmask of the new call state which is guaranteed to be different than the previous state. The state is set
to 0 when the call is paused. The bitmask represents all the activities currently performed by the friend.
pointer (c_void_p) to user_data
:param user_data: pointer (c_void_p) to user data
"""
if callback is None:
self.libtoxav.toxav_callback_call_state(self._toxav_pointer, POINTER(None)(), user_data)
self.call_state_cb = None
return
LOG_DEBUG(f"callback_call_state")
c_callback = CFUNCTYPE(None, c_void_p, c_uint32, c_uint32, c_void_p)
self.call_state_cb = c_callback(callback)
self.libtoxav.toxav_callback_call_state(self._toxav_pointer, self.call_state_cb, user_data)
# -----------------------------------------------------------------------------------------------------------------
# Call control
# -----------------------------------------------------------------------------------------------------------------
def call_control(self, friend_number, control):
"""
Sends a call control command to a friend.
:param friend_number: The friend number of the friend this client is in a call with.
:param control: The control command to send.
:return: True on success.
"""
toxav_err_call_control = c_int()
LOG_DEBUG(f"call_control")
result = self.libtoxav.toxav_call_control(self._toxav_pointer, c_uint32(friend_number), c_int(control),
byref(toxav_err_call_control))
toxav_err_call_control = toxav_err_call_control.value
if toxav_err_call_control == TOXAV_ERR_CALL_CONTROL['OK']:
return bool(result)
elif toxav_err_call_control == TOXAV_ERR_CALL_CONTROL['SYNC']:
raise RuntimeError('Synchronization error occurred.')
elif toxav_err_call_control == TOXAV_ERR_CALL_CONTROL['FRIEND_NOT_FOUND']:
raise ArgumentError('The friend_number passed did not designate a valid friend.')
elif toxav_err_call_control == TOXAV_ERR_CALL_CONTROL['FRIEND_NOT_IN_CALL']:
raise RuntimeError('This client is currently not in a call with the friend. Before the call is answered, '
'only CANCEL is a valid control.')
elif toxav_err_call_control == TOXAV_ERR_CALL_CONTROL['INVALID_TRANSITION']:
raise RuntimeError('Happens if user tried to pause an already paused call or if trying to resume a call '
'that is not paused.')
# -----------------------------------------------------------------------------------------------------------------
# TODO Controlling bit rates
# -----------------------------------------------------------------------------------------------------------------
# -----------------------------------------------------------------------------------------------------------------
# A/V sending
# -----------------------------------------------------------------------------------------------------------------
def audio_send_frame(self, friend_number, pcm, sample_count, channels, sampling_rate):
"""
Send an audio frame to a friend.
The expected format of the PCM data is: [s1c1][s1c2][...][s2c1][s2c2][...]...
Meaning: sample 1 for channel 1, sample 1 for channel 2, ...
For mono audio, this has no meaning, every sample is subsequent. For stereo, this means the expected format is
LRLRLR... with samples for left and right alternating.
:param friend_number: The friend number of the friend to which to send an audio frame.
:param pcm: An array of audio samples. The size of this array must be sample_count * channels.
:param sample_count: Number of samples in this frame. Valid numbers here are
((sample rate) * (audio length) / 1000), where audio length can be 2.5, 5, 10, 20, 40 or 60 milliseconds.
:param channels: Number of audio channels. Sulpported values are 1 and 2.
:param sampling_rate: Audio sampling rate used in this frame. Valid sampling rates are 8000, 12000, 16000,
24000, or 48000.
"""
toxav_err_send_frame = c_int()
LOG_TRACE(f"toxav_audio_send_frame")
assert sampling_rate in [8000, 12000, 16000, 24000, 48000]
result = self.libtoxav.toxav_audio_send_frame(self._toxav_pointer,
c_uint32(friend_number),
cast(pcm, c_void_p),
c_size_t(sample_count), c_uint8(channels),
c_uint32(sampling_rate), byref(toxav_err_send_frame))
toxav_err_send_frame = toxav_err_send_frame.value
if toxav_err_send_frame == TOXAV_ERR_SEND_FRAME['OK']:
return bool(result)
elif toxav_err_send_frame == TOXAV_ERR_SEND_FRAME['NULL']:
raise ArgumentError('The samples data pointer was NULL.')
elif toxav_err_send_frame == TOXAV_ERR_SEND_FRAME['FRIEND_NOT_FOUND']:
raise ArgumentError('The friend_number passed did not designate a valid friend.')
elif toxav_err_send_frame == TOXAV_ERR_SEND_FRAME['FRIEND_NOT_IN_CALL']:
raise RuntimeError('This client is currently not in a call with the friend.')
elif toxav_err_send_frame == TOXAV_ERR_SEND_FRAME['SYNC']:
raise RuntimeError('Synchronization error occurred.')
elif toxav_err_send_frame == TOXAV_ERR_SEND_FRAME['INVALID']:
raise ArgumentError('One of the frame parameters was invalid. E.g. the resolution may be too small or too '
'large, or the audio sampling rate may be unsupported.')
elif toxav_err_send_frame == TOXAV_ERR_SEND_FRAME['PAYLOAD_TYPE_DISABLED']:
raise RuntimeError('Either friend turned off audio or video receiving or we turned off sending for the said'
'payload.')
elif toxav_err_send_frame == TOXAV_ERR_SEND_FRAME['RTP_FAILED']:
RuntimeError('Failed to push frame through rtp interface.')
def video_send_frame(self, friend_number, width, height, y, u, v):
"""
Send a video frame to a friend.
Y - plane should be of size: height * width
U - plane should be of size: (height/2) * (width/2)
V - plane should be of size: (height/2) * (width/2)
:param friend_number: The friend number of the friend to which to send a video frame.
:param width: Width of the frame in pixels.
:param height: Height of the frame in pixels.
:param y: Y (Luminance) plane data.
:param u: U (Chroma) plane data.
:param v: V (Chroma) plane data.
"""
toxav_err_send_frame = c_int()
LOG_TRACE(f"toxav_video_send_frame")
result = self.libtoxav.toxav_video_send_frame(self._toxav_pointer, c_uint32(friend_number), c_uint16(width),
c_uint16(height), c_char_p(y), c_char_p(u), c_char_p(v),
byref(toxav_err_send_frame))
toxav_err_send_frame = toxav_err_send_frame.value
if toxav_err_send_frame == TOXAV_ERR_SEND_FRAME['OK']:
return bool(result)
elif toxav_err_send_frame == TOXAV_ERR_SEND_FRAME['NULL']:
raise ArgumentError('One of Y, U, or V was NULL.')
elif toxav_err_send_frame == TOXAV_ERR_SEND_FRAME['FRIEND_NOT_FOUND']:
raise ArgumentError('The friend_number passed did not designate a valid friend.')
elif toxav_err_send_frame == TOXAV_ERR_SEND_FRAME['FRIEND_NOT_IN_CALL']:
raise RuntimeError('This client is currently not in a call with the friend.')
elif toxav_err_send_frame == TOXAV_ERR_SEND_FRAME['SYNC']:
raise RuntimeError('Synchronization error occurred.')
elif toxav_err_send_frame == TOXAV_ERR_SEND_FRAME['INVALID']:
raise ArgumentError('One of the frame parameters was invalid. E.g. the resolution may be too small or too '
'large, or the audio sampling rate may be unsupported.')
elif toxav_err_send_frame == TOXAV_ERR_SEND_FRAME['PAYLOAD_TYPE_DISABLED']:
raise RuntimeError('Either friend turned off audio or video receiving or we turned off sending for the said'
'payload.')
elif toxav_err_send_frame == TOXAV_ERR_SEND_FRAME['RTP_FAILED']:
RuntimeError('Failed to push frame through rtp interface.')
# -----------------------------------------------------------------------------------------------------------------
# A/V receiving
# -----------------------------------------------------------------------------------------------------------------
def callback_audio_receive_frame(self, callback, user_data):
"""
Set the callback for the `audio_receive_frame` event. Pass None to unset.
:param callback: Python function.
Function for the audio_receive_frame callback. The callback can be called multiple times per single
iteration depending on the amount of queued frames in the buffer. The received format is the same as in send
function.
Should take pointer (c_void_p) to ToxAV object,
The friend number (c_uint32) of the friend who sent an audio frame.
An array (c_uint8) of audio samples (sample_count * channels elements).
The number (c_size_t) of audio samples per channel in the PCM array.
Number (c_uint8) of audio channels.
Sampling rate (c_uint32) used in this frame.
pointer (c_void_p) to user_data
:param user_data: pointer (c_void_p) to user data
"""
if callback is None:
self.libtoxav.toxav_callback_audio_receive_frame(self._toxav_pointer, POINTER(None)(), user_data)
self.audio_receive_frame_cb = None
return
LOG_DEBUG(f"toxav_callback_audio_receive_frame")
c_callback = CFUNCTYPE(None, c_void_p, c_uint32, POINTER(c_uint8), c_size_t, c_uint8, c_uint32, c_void_p)
self.audio_receive_frame_cb = c_callback(callback)
self.libtoxav.toxav_callback_audio_receive_frame(self._toxav_pointer, self.audio_receive_frame_cb, user_data)
def callback_video_receive_frame(self, callback, user_data):
"""
Set the callback for the `video_receive_frame` event. Pass None to unset.
:param callback: Python function.
The function type for the video_receive_frame callback.
Should take
toxAV pointer (c_void_p) to ToxAV object,
friend_number The friend number (c_uint32) of the friend who sent a video frame.
width Width (c_uint16) of the frame in pixels.
height Height (c_uint16) of the frame in pixels.
y
u
v Plane data (POINTER(c_uint8)).
The size of plane data is derived from width and height where
Y = MAX(width, abs(ystride)) * height,
U = MAX(width/2, abs(ustride)) * (height/2) and
V = MAX(width/2, abs(vstride)) * (height/2).
ystride
ustride
vstride Strides data (c_int32). Strides represent padding for each plane that may or may not be present. You must
handle strides in your image processing code. Strides are negative if the image is bottom-up
hence why you MUST abs() it when calculating plane buffer size.
user_data pointer (c_void_p) to user_data
:param user_data: pointer (c_void_p) to user data
"""
if callback is None:
self.libtoxav.toxav_callback_video_receive_frame(self._toxav_pointer, POINTER(None)(), user_data)
self.video_receive_frame_cb = None
return
LOG_DEBUG(f"toxav_callback_video_receive_frame")
c_callback = CFUNCTYPE(None, c_void_p, c_uint32, c_uint16, c_uint16,
POINTER(c_uint8), POINTER(c_uint8), POINTER(c_uint8),
c_int32, c_int32, c_int32,
c_void_p)
self.video_receive_frame_cb = c_callback(callback)
self.libtoxav.toxav_callback_video_receive_frame(self._toxav_pointer, self.video_receive_frame_cb, user_data)

View File

@ -1,133 +0,0 @@
# -*- mode: python; indent-tabs-mode: nil; py-indent-offset: 4; coding: utf-8 -*-
TOXAV_ERR_NEW = {
# The function returned successfully.
'OK': 0,
# One of the arguments to the function was NULL when it was not expected.
'NULL': 1,
# Memory allocation failure while trying to allocate structures required for the A/V session.
'MALLOC': 2,
# Attempted to create a second session for the same Tox instance.
'MULTIPLE': 3,
}
TOXAV_ERR_CALL = {
# The function returned successfully.
'OK': 0,
# A resource allocation error occurred while trying to create the structures required for the call.
'MALLOC': 1,
# Synchronization error occurred.
'SYNC': 2,
# The friend number did not designate a valid friend.
'FRIEND_NOT_FOUND': 3,
# The friend was valid, but not currently connected.
'FRIEND_NOT_CONNECTED': 4,
# Attempted to call a friend while already in an audio or video call with them.
'FRIEND_ALREADY_IN_CALL': 5,
# Audio or video bit rate is invalid.
'INVALID_BIT_RATE': 6,
}
TOXAV_ERR_ANSWER = {
# The function returned successfully.
'OK': 0,
# Synchronization error occurred.
'SYNC': 1,
# Failed to initialize codecs for call session. Note that codec initiation will fail if there is no receive callback
# registered for either audio or video.
'CODEC_INITIALIZATION': 2,
# The friend number did not designate a valid friend.
'FRIEND_NOT_FOUND': 3,
# The friend was valid, but they are not currently trying to initiate a call. This is also returned if this client
# is already in a call with the friend.
'FRIEND_NOT_CALLING': 4,
# Audio or video bit rate is invalid.
'INVALID_BIT_RATE': 5,
}
TOXAV_FRIEND_CALL_STATE = {
# Set by the AV core if an error occurred on the remote end or if friend timed out. This is the final state after
# which no more state transitions can occur for the call. This call state will never be triggered in combination
# with other call states.
'ERROR': 1,
# The call has finished. This is the final state after which no more state transitions can occur for the call. This
# call state will never be triggered in combination with other call states.
'FINISHED': 2,
# The flag that marks that friend is sending audio.
'SENDING_A': 4,
# The flag that marks that friend is sending video.
'SENDING_V': 8,
# The flag that marks that friend is receiving audio.
'ACCEPTING_A': 16,
# The flag that marks that friend is receiving video.
'ACCEPTING_V': 32,
}
TOXAV_CALL_CONTROL = {
# Resume a previously paused call. Only valid if the pause was caused by this client, if not, this control is
# ignored. Not valid before the call is accepted.
'RESUME': 0,
# Put a call on hold. Not valid before the call is accepted.
'PAUSE': 1,
# Reject a call if it was not answered, yet. Cancel a call after it was answered.
'CANCEL': 2,
# Request that the friend stops sending audio. Regardless of the friend's compliance, this will cause the
# audio_receive_frame event to stop being triggered on receiving an audio frame from the friend.
'MUTE_AUDIO': 3,
# Calling this control will notify client to start sending audio again.
'UNMUTE_AUDIO': 4,
# Request that the friend stops sending video. Regardless of the friend's compliance, this will cause the
# video_receive_frame event to stop being triggered on receiving a video frame from the friend.
'HIDE_VIDEO': 5,
# Calling this control will notify client to start sending video again.
'SHOW_VIDEO': 6,
}
TOXAV_ERR_CALL_CONTROL = {
# The function returned successfully.
'OK': 0,
# Synchronization error occurred.
'SYNC': 1,
# The friend_number passed did not designate a valid friend.
'FRIEND_NOT_FOUND': 2,
# This client is currently not in a call with the friend. Before the call is answered, only CANCEL is a valid
# control.
'FRIEND_NOT_IN_CALL': 3,
# Happens if user tried to pause an already paused call or if trying to resume a call that is not paused.
'INVALID_TRANSITION': 4,
}
TOXAV_ERR_BIT_RATE_SET = {
# The function returned successfully.
'OK': 0,
# Synchronization error occurred.
'SYNC': 1,
# The audio bit rate passed was not one of the supported values.
'INVALID_AUDIO_BIT_RATE': 2,
# The video bit rate passed was not one of the supported values.
'INVALID_VIDEO_BIT_RATE': 3,
# The friend_number passed did not designate a valid friend.
'FRIEND_NOT_FOUND': 4,
# This client is currently not in a call with the friend.
'FRIEND_NOT_IN_CALL': 5,
}
TOXAV_ERR_SEND_FRAME = {
# The function returned successfully.
'OK': 0,
# In case of video, one of Y, U, or V was NULL. In case of audio, the samples data pointer was NULL.
'NULL': 1,
# The friend_number passed did not designate a valid friend.
'FRIEND_NOT_FOUND': 2,
# This client is currently not in a call with the friend.
'FRIEND_NOT_IN_CALL': 3,
# Synchronization error occurred.
'SYNC': 4,
# One of the frame parameters was invalid. E.g. the resolution may be too small or too large, or the audio sampling
# rate may be unsupported.
'INVALID': 5,
# Either friend turned off audio or video receiving or we turned off sending for the said payload.
'PAYLOAD_TYPE_DISABLED': 6,
# Failed to push frame through rtp interface.
'RTP_FAILED': 7,
}

View File

@ -1,957 +0,0 @@
# -*- mode: python; indent-tabs-mode: nil; py-indent-offset: 4; coding: utf-8 -*-
TOX_USER_STATUS = {
'NONE': 0,
'AWAY': 1,
'BUSY': 2,
}
TOX_MESSAGE_TYPE = {
'NORMAL': 0,
'ACTION': 1,
}
TOX_PROXY_TYPE = {
'NONE': 0,
'HTTP': 1,
'SOCKS5': 2,
}
TOX_SAVEDATA_TYPE = {
'NONE': 0,
'TOX_SAVE': 1,
'SECRET_KEY': 2,
}
TOX_ERR_OPTIONS_NEW = {
'OK': 0,
'MALLOC': 1,
}
TOX_ERR_NEW = {
'OK': 0,
'NULL': 1,
'MALLOC': 2,
'PORT_ALLOC': 3,
'PROXY_BAD_TYPE': 4,
'PROXY_BAD_HOST': 5,
'PROXY_BAD_PORT': 6,
'PROXY_NOT_FOUND': 7,
'LOAD_ENCRYPTED': 8,
'LOAD_BAD_FORMAT': 9,
'TCP_SERVER_ALLOC': 10,
}
TOX_ERR_BOOTSTRAP = {
'OK': 0,
'NULL': 1,
'BAD_HOST': 2,
'BAD_PORT': 3,
}
TOX_CONNECTION = {
'NONE': 0,
'TCP': 1,
'UDP': 2,
}
TOX_ERR_SET_INFO = {
'OK': 0,
'NULL': 1,
'TOO_LONG': 2,
}
TOX_ERR_FRIEND_ADD = {
'OK': 0,
'NULL': 1,
'TOO_LONG': 2,
'NO_MESSAGE': 3,
'OWN_KEY': 4,
'ALREADY_SENT': 5,
'BAD_CHECKSUM': 6,
'SET_NEW_NOSPAM': 7,
'MALLOC': 8,
}
TOX_ERR_FRIEND_DELETE = {
'OK': 0,
'FRIEND_NOT_FOUND': 1,
}
TOX_ERR_FRIEND_BY_PUBLIC_KEY = {
'OK': 0,
'NULL': 1,
'NOT_FOUND': 2,
}
TOX_ERR_FRIEND_GET_PUBLIC_KEY = {
'OK': 0,
'FRIEND_NOT_FOUND': 1,
}
TOX_ERR_FRIEND_GET_LAST_ONLINE = {
'OK': 0,
'FRIEND_NOT_FOUND': 1,
}
TOX_ERR_FRIEND_QUERY = {
'OK': 0,
'NULL': 1,
'FRIEND_NOT_FOUND': 2,
}
TOX_ERR_SET_TYPING = {
'OK': 0,
'FRIEND_NOT_FOUND': 1,
}
TOX_ERR_FRIEND_SEND_MESSAGE = {
'OK': 0,
'NULL': 1,
'FRIEND_NOT_FOUND': 2,
'FRIEND_NOT_CONNECTED': 3,
'SENDQ': 4,
'TOO_LONG': 5,
'EMPTY': 6,
}
TOX_FILE_KIND = {
'DATA': 0,
'AVATAR': 1,
}
TOX_FILE_CONTROL = {
'RESUME': 0,
'PAUSE': 1,
'CANCEL': 2,
}
TOX_ERR_FILE_CONTROL = {
'OK': 0,
'FRIEND_NOT_FOUND': 1,
'FRIEND_NOT_CONNECTED': 2,
'NOT_FOUND': 3,
'NOT_PAUSED': 4,
'DENIED': 5,
'ALREADY_PAUSED': 6,
'SENDQ': 7,
}
TOX_ERR_FILE_SEEK = {
'OK': 0,
'FRIEND_NOT_FOUND': 1,
'FRIEND_NOT_CONNECTED': 2,
'NOT_FOUND': 3,
'DENIED': 4,
'INVALID_POSITION': 5,
'SENDQ': 6,
}
TOX_ERR_FILE_GET = {
'OK': 0,
'NULL': 1,
'FRIEND_NOT_FOUND': 2,
'NOT_FOUND': 3,
}
TOX_ERR_FILE_SEND = {
'OK': 0,
'NULL': 1,
'FRIEND_NOT_FOUND': 2,
'FRIEND_NOT_CONNECTED': 3,
'NAME_TOO_LONG': 4,
'TOO_MANY': 5,
}
TOX_ERR_FILE_SEND_CHUNK = {
'OK': 0,
'NULL': 1,
'FRIEND_NOT_FOUND': 2,
'FRIEND_NOT_CONNECTED': 3,
'NOT_FOUND': 4,
'NOT_TRANSFERRING': 5,
'INVALID_LENGTH': 6,
'SENDQ': 7,
'WRONG_POSITION': 8,
}
TOX_ERR_FRIEND_CUSTOM_PACKET = {
'OK': 0,
'NULL': 1,
'FRIEND_NOT_FOUND': 2,
'FRIEND_NOT_CONNECTED': 3,
'INVALID': 4,
'EMPTY': 5,
'TOO_LONG': 6,
'SENDQ': 7,
}
TOX_ERR_GET_PORT = {
'OK': 0,
'NOT_BOUND': 1,
}
TOX_GROUP_PRIVACY_STATE = {
#
# The group is considered to be public. Anyone may join the group using the Chat ID.
#
# If the group is in this state, even if the Chat ID is never explicitly shared
# with someone outside of the group, information including the Chat ID, IP addresses,
# and peer ID's (but not Tox ID's) is visible to anyone with access to a node
# storing a DHT entry for the given group.
#
'PUBLIC': 0,
#
# The group is considered to be private. The only way to join the group is by having
# someone in your contact list send you an invite.
#
# If the group is in this state, no group information (mentioned above) is present in the DHT;
# the DHT is not used for any purpose at all. If a public group is set to private,
# all DHT information related to the group will expire shortly.
#
'PRIVATE': 1
}
TOX_GROUP_ROLE = {
#
# May kick and ban all other peers as well as set their role to anything (except founder).
# Founders may also set the group password, toggle the privacy state, and set the peer limit.
#
'FOUNDER': 0,
#
# May kick, ban and set the user and observer roles for peers below this role.
# May also set the group topic.
#
'MODERATOR': 1,
#
# May communicate with other peers normally.
#
'USER': 2,
#
# May observe the group and ignore peers; may not communicate with other peers or with the group.
#
'OBSERVER': 3
}
TOX_ERR_GROUP_NEW = {
#
# The function returned successfully.
#
'TOX_ERR_GROUP_NEW_OK': 0,
#
# The group name exceeded TOX_GROUP_MAX_GROUP_NAME_LENGTH.
#
'TOX_ERR_GROUP_NEW_TOO_LONG': 1,
#
# group_name is NULL or length is zero.
#
'TOX_ERR_GROUP_NEW_EMPTY': 2,
#
# TOX_GROUP_PRIVACY_STATE is an invalid type.
#
'TOX_ERR_GROUP_NEW_PRIVACY': 3,
#
# The group instance failed to initialize.
#
'TOX_ERR_GROUP_NEW_INIT': 4,
#
# The group state failed to initialize. This usually indicates that something went wrong
# related to cryptographic signing.
#
'TOX_ERR_GROUP_NEW_STATE': 5,
#
# The group failed to announce to the DHT. This indicates a network related error.
#
'TOX_ERR_GROUP_NEW_ANNOUNCE': 6,
}
TOX_ERR_GROUP_JOIN = {
#
# The function returned successfully.
#
'TOX_ERR_GROUP_JOIN_OK': 0,
#
# The group instance failed to initialize.
#
'TOX_ERR_GROUP_JOIN_INIT': 1,
#
# The chat_id pointer is set to NULL or a group with chat_id already exists. This usually
# happens if the client attempts to create multiple sessions for the same group.
#
'TOX_ERR_GROUP_JOIN_BAD_CHAT_ID': 2,
#
# Password length exceeded TOX_GROUP_MAX_PASSWORD_SIZE.
#
'TOX_ERR_GROUP_JOIN_TOO_LONG': 3,
}
TOX_ERR_GROUP_RECONNECT = {
#
# The function returned successfully.
#
'TOX_ERR_GROUP_RECONNECT_OK': 0,
#
# The group number passed did not designate a valid group.
#
'TOX_ERR_GROUP_RECONNECT_GROUP_NOT_FOUND': 1,
}
TOX_ERR_GROUP_LEAVE = {
#
# The function returned successfully.
#
'TOX_ERR_GROUP_LEAVE_OK': 0,
#
# The group number passed did not designate a valid group.
#
'TOX_ERR_GROUP_LEAVE_GROUP_NOT_FOUND': 1,
#
# Message length exceeded 'TOX_GROUP_MAX_PART_LENGTH.
#
'TOX_ERR_GROUP_LEAVE_TOO_LONG': 2,
#
# The parting packet failed to send.
#
'TOX_ERR_GROUP_LEAVE_FAIL_SEND': 3,
#
# The group chat instance failed to be deleted. This may occur due to memory related errors.
#
'TOX_ERR_GROUP_LEAVE_DELETE_FAIL': 4,
}
TOX_ERR_GROUP_SELF_QUERY = {
#
# The function returned successfully.
#
'TOX_ERR_GROUP_SELF_QUERY_OK': 0,
#
# The group number passed did not designate a valid group.
#
'TOX_ERR_GROUP_SELF_QUERY_GROUP_NOT_FOUND': 1,
}
TOX_ERR_GROUP_SELF_NAME_SET = {
#
# The function returned successfully.
#
'TOX_ERR_GROUP_SELF_NAME_SET_OK': 0,
#
# The group number passed did not designate a valid group.
#
'TOX_ERR_GROUP_SELF_NAME_SET_GROUP_NOT_FOUND': 1,
#
# Name length exceeded 'TOX_MAX_NAME_LENGTH.
#
'TOX_ERR_GROUP_SELF_NAME_SET_TOO_LONG': 2,
#
# The length given to the set function is zero or name is a NULL pointer.
#
'TOX_ERR_GROUP_SELF_NAME_SET_INVALID': 3,
#
# The name is already taken by another peer in the group.
#
'TOX_ERR_GROUP_SELF_NAME_SET_TAKEN': 4,
#
# The packet failed to send.
#
'TOX_ERR_GROUP_SELF_NAME_SET_FAIL_SEND': 5
}
TOX_ERR_GROUP_SELF_STATUS_SET = {
#
# The function returned successfully.
#
'TOX_ERR_GROUP_SELF_STATUS_SET_OK': 0,
#
# The group number passed did not designate a valid group.
#
'TOX_ERR_GROUP_SELF_STATUS_SET_GROUP_NOT_FOUND': 1,
#
# An invalid type was passed to the set function.
#
'TOX_ERR_GROUP_SELF_STATUS_SET_INVALID': 2,
#
# The packet failed to send.
#
'TOX_ERR_GROUP_SELF_STATUS_SET_FAIL_SEND': 3
}
TOX_ERR_GROUP_PEER_QUERY = {
#
# The function returned successfully.
#
'TOX_ERR_GROUP_PEER_QUERY_OK': 0,
#
# The group number passed did not designate a valid group.
#
'TOX_ERR_GROUP_PEER_QUERY_GROUP_NOT_FOUND': 1,
#
# The ID passed did not designate a valid peer.
#
'TOX_ERR_GROUP_PEER_QUERY_PEER_NOT_FOUND': 2
}
TOX_ERR_GROUP_STATE_QUERIES = {
#
# The function returned successfully.
#
'TOX_ERR_GROUP_STATE_QUERIES_OK': 0,
#
# The group number passed did not designate a valid group.
#
'TOX_ERR_GROUP_STATE_QUERIES_GROUP_NOT_FOUND': 1
}
TOX_ERR_GROUP_TOPIC_SET = {
#
# The function returned successfully.
#
'TOX_ERR_GROUP_TOPIC_SET_OK': 0,
#
# The group number passed did not designate a valid group.
#
'TOX_ERR_GROUP_TOPIC_SET_GROUP_NOT_FOUND': 1,
#
# Topic length exceeded 'TOX_GROUP_MAX_TOPIC_LENGTH.
#
'TOX_ERR_GROUP_TOPIC_SET_TOO_LONG': 2,
#
# The caller does not have the required permissions to set the topic.
#
'TOX_ERR_GROUP_TOPIC_SET_PERMISSIONS': 3,
#
# The packet could not be created. This error is usually related to cryptographic signing.
#
'TOX_ERR_GROUP_TOPIC_SET_FAIL_CREATE': 4,
#
# The packet failed to send.
#
'TOX_ERR_GROUP_TOPIC_SET_FAIL_SEND': 5
}
TOX_ERR_GROUP_SEND_MESSAGE = {
#
# The function returned successfully.
#
'TOX_ERR_GROUP_SEND_MESSAGE_OK': 0,
#
# The group number passed did not designate a valid group.
#
'TOX_ERR_GROUP_SEND_MESSAGE_GROUP_NOT_FOUND': 1,
#
# Message length exceeded 'TOX_MAX_MESSAGE_LENGTH.
#
'TOX_ERR_GROUP_SEND_MESSAGE_TOO_LONG': 2,
#
# The message pointer is null or length is zero.
#
'TOX_ERR_GROUP_SEND_MESSAGE_EMPTY': 3,
#
# The message type is invalid.
#
'TOX_ERR_GROUP_SEND_MESSAGE_BAD_TYPE': 4,
#
# The caller does not have the required permissions to send group messages.
#
'TOX_ERR_GROUP_SEND_MESSAGE_PERMISSIONS': 5,
#
# Packet failed to send.
#
'TOX_ERR_GROUP_SEND_MESSAGE_FAIL_SEND': 6
}
TOX_ERR_GROUP_SEND_PRIVATE_MESSAGE = {
#
# The function returned successfully.
#
'TOX_ERR_GROUP_SEND_PRIVATE_MESSAGE_OK': 0,
#
# The group number passed did not designate a valid group.
#
'TOX_ERR_GROUP_SEND_PRIVATE_MESSAGE_GROUP_NOT_FOUND': 1,
#
# The ID passed did not designate a valid peer.
#
'TOX_ERR_GROUP_SEND_PRIVATE_MESSAGE_PEER_NOT_FOUND': 2,
#
# Message length exceeded 'TOX_MAX_MESSAGE_LENGTH.
#
'TOX_ERR_GROUP_SEND_PRIVATE_MESSAGE_TOO_LONG': 3,
#
# The message pointer is null or length is zero.
#
'TOX_ERR_GROUP_SEND_PRIVATE_MESSAGE_EMPTY': 4,
#
# The caller does not have the required permissions to send group messages.
#
'TOX_ERR_GROUP_SEND_PRIVATE_MESSAGE_PERMISSIONS': 5,
#
# Packet failed to send.
#
'TOX_ERR_GROUP_SEND_PRIVATE_MESSAGE_FAIL_SEND': 6
}
TOX_ERR_GROUP_SEND_CUSTOM_PACKET = {
#
# The function returned successfully.
#
'TOX_ERR_GROUP_SEND_CUSTOM_PACKET_OK': 0,
#
# The group number passed did not designate a valid group.
#
'TOX_ERR_GROUP_SEND_CUSTOM_PACKET_GROUP_NOT_FOUND': 1,
#
# Message length exceeded 'TOX_MAX_MESSAGE_LENGTH.
#
'TOX_ERR_GROUP_SEND_CUSTOM_PACKET_TOO_LONG': 2,
#
# The message pointer is null or length is zero.
#
'TOX_ERR_GROUP_SEND_CUSTOM_PACKET_EMPTY': 3,
#
# The caller does not have the required permissions to send group messages.
#
'TOX_ERR_GROUP_SEND_CUSTOM_PACKET_PERMISSIONS': 4
}
TOX_ERR_GROUP_INVITE_FRIEND = {
#
# The function returned successfully.
#
'TOX_ERR_GROUP_INVITE_FRIEND_OK': 0,
#
# The group number passed did not designate a valid group.
#
'TOX_ERR_GROUP_INVITE_FRIEND_GROUP_NOT_FOUND': 1,
#
# The friend number passed did not designate a valid friend.
#
'TOX_ERR_GROUP_INVITE_FRIEND_FRIEND_NOT_FOUND': 2,
#
# Creation of the invite packet failed. This indicates a network related error.
#
'TOX_ERR_GROUP_INVITE_FRIEND_INVITE_FAIL': 3,
#
# Packet failed to send.
#
'TOX_ERR_GROUP_INVITE_FRIEND_FAIL_SEND': 4
}
TOX_ERR_GROUP_INVITE_ACCEPT = {
#
# The function returned successfully.
#
'TOX_ERR_GROUP_INVITE_ACCEPT_OK': 0,
#
# The invite data is not in the expected format.
#
'TOX_ERR_GROUP_INVITE_ACCEPT_BAD_INVITE': 1,
#
# The group instance failed to initialize.
#
'TOX_ERR_GROUP_INVITE_ACCEPT_INIT_FAILED': 2,
#
# Password length exceeded 'TOX_GROUP_MAX_PASSWORD_SIZE.
#
'TOX_ERR_GROUP_INVITE_ACCEPT_TOO_LONG': 3
}
TOX_GROUP_JOIN_FAIL = {
#
# You are using the same nickname as someone who is already in the group.
#
'TOX_GROUP_JOIN_FAIL_NAME_TAKEN': 0,
#
# The group peer limit has been reached.
#
'TOX_GROUP_JOIN_FAIL_PEER_LIMIT': 1,
#
# You have supplied an invalid password.
#
'TOX_GROUP_JOIN_FAIL_INVALID_PASSWORD': 2,
#
# The join attempt failed due to an unspecified error. This often occurs when the group is
# not found in the DHT.
#
'TOX_GROUP_JOIN_FAIL_UNKNOWN': 3
}
TOX_ERR_GROUP_FOUNDER_SET_PASSWORD = {
#
# The function returned successfully.
#
'TOX_ERR_GROUP_FOUNDER_SET_PASSWORD_OK': 0,
#
# The group number passed did not designate a valid group.
#
'TOX_ERR_GROUP_FOUNDER_SET_PASSWORD_GROUP_NOT_FOUND': 1,
#
# The caller does not have the required permissions to set the password.
#
'TOX_ERR_GROUP_FOUNDER_SET_PASSWORD_PERMISSIONS': 2,
#
# Password length exceeded 'TOX_GROUP_MAX_PASSWORD_SIZE.
#
'TOX_ERR_GROUP_FOUNDER_SET_PASSWORD_TOO_LONG': 3,
#
# The packet failed to send.
#
'TOX_ERR_GROUP_FOUNDER_SET_PASSWORD_FAIL_SEND': 4
}
TOX_ERR_GROUP_FOUNDER_SET_PRIVACY_STATE = {
#
# The function returned successfully.
#
'TOX_ERR_GROUP_FOUNDER_SET_PRIVACY_STATE_OK': 0,
#
# The group number passed did not designate a valid group.
#
'TOX_ERR_GROUP_FOUNDER_SET_PRIVACY_STATE_GROUP_NOT_FOUND': 1,
#
# 'TOX_GROUP_PRIVACY_STATE is an invalid type.
#
'TOX_ERR_GROUP_FOUNDER_SET_PRIVACY_STATE_INVALID': 2,
#
# The caller does not have the required permissions to set the privacy state.
#
'TOX_ERR_GROUP_FOUNDER_SET_PRIVACY_STATE_PERMISSIONS': 3,
#
# The privacy state could not be set. This may occur due to an error related to
# cryptographic signing of the new shared state.
#
'TOX_ERR_GROUP_FOUNDER_SET_PRIVACY_STATE_FAIL_SET': 4,
#
# The packet failed to send.
#
'TOX_ERR_GROUP_FOUNDER_SET_PRIVACY_STATE_FAIL_SEND': 5
}
TOX_ERR_GROUP_FOUNDER_SET_PEER_LIMIT = {
#
# The function returned successfully.
#
'TOX_ERR_GROUP_FOUNDER_SET_PEER_LIMIT_OK': 0,
#
# The group number passed did not designate a valid group.
#
'TOX_ERR_GROUP_FOUNDER_SET_PEER_LIMIT_GROUP_NOT_FOUND': 1,
#
# The caller does not have the required permissions to set the peer limit.
#
'TOX_ERR_GROUP_FOUNDER_SET_PEER_LIMIT_PERMISSIONS': 2,
#
# The peer limit could not be set. This may occur due to an error related to
# cryptographic signing of the new shared state.
#
'TOX_ERR_GROUP_FOUNDER_SET_PEER_LIMIT_FAIL_SET': 3,
#
# The packet failed to send.
#
'TOX_ERR_GROUP_FOUNDER_SET_PEER_LIMIT_FAIL_SEND': 4
}
TOX_ERR_GROUP_TOGGLE_IGNORE = {
#
# The function returned successfully.
#
'TOX_ERR_GROUP_TOGGLE_IGNORE_OK': 0,
#
# The group number passed did not designate a valid group.
#
'TOX_ERR_GROUP_TOGGLE_IGNORE_GROUP_NOT_FOUND': 1,
#
# The ID passed did not designate a valid peer.
#
'TOX_ERR_GROUP_TOGGLE_IGNORE_PEER_NOT_FOUND': 2
}
TOX_ERR_GROUP_MOD_SET_ROLE = {
#
# The function returned successfully.
#
'TOX_ERR_GROUP_MOD_SET_ROLE_OK': 0,
#
# The group number passed did not designate a valid group.
#
'TOX_ERR_GROUP_MOD_SET_ROLE_GROUP_NOT_FOUND': 1,
#
# The ID passed did not designate a valid peer. Note: you cannot set your own role.
#
'TOX_ERR_GROUP_MOD_SET_ROLE_PEER_NOT_FOUND': 2,
#
# The caller does not have the required permissions for this action.
#
'TOX_ERR_GROUP_MOD_SET_ROLE_PERMISSIONS': 3,
#
# The role assignment is invalid. This will occur if you try to set a peer's role to
# the role they already have.
#
'TOX_ERR_GROUP_MOD_SET_ROLE_ASSIGNMENT': 4,
#
# The role was not successfully set. This may occur if something goes wrong with role setting': ,
# or if the packet fails to send.
#
'TOX_ERR_GROUP_MOD_SET_ROLE_FAIL_ACTION': 5
}
TOX_ERR_GROUP_MOD_REMOVE_PEER = {
#
# The function returned successfully.
#
'TOX_ERR_GROUP_MOD_REMOVE_PEER_OK': 0,
#
# The group number passed did not designate a valid group.
#
'TOX_ERR_GROUP_MOD_REMOVE_PEER_GROUP_NOT_FOUND': 1,
#
# The ID passed did not designate a valid peer.
#
'TOX_ERR_GROUP_MOD_REMOVE_PEER_PEER_NOT_FOUND': 2,
#
# The caller does not have the required permissions for this action.
#
'TOX_ERR_GROUP_MOD_REMOVE_PEER_PERMISSIONS': 3,
#
# The peer could not be removed from the group.
#
# If a ban was set': , this error indicates that the ban entry could not be created.
# This is usually due to the peer's IP address already occurring in the ban list. It may also
# be due to the entry containing invalid peer information': , or a failure to cryptographically
# authenticate the entry.
#
'TOX_ERR_GROUP_MOD_REMOVE_PEER_FAIL_ACTION': 4,
#
# The packet failed to send.
#
'TOX_ERR_GROUP_MOD_REMOVE_PEER_FAIL_SEND': 5
}
TOX_ERR_GROUP_MOD_REMOVE_BAN = {
#
# The function returned successfully.
#
'TOX_ERR_GROUP_MOD_REMOVE_BAN_OK': 0,
#
# The group number passed did not designate a valid group.
#
'TOX_ERR_GROUP_MOD_REMOVE_BAN_GROUP_NOT_FOUND': 1,
#
# The caller does not have the required permissions for this action.
#
'TOX_ERR_GROUP_MOD_REMOVE_BAN_PERMISSIONS': 2,
#
# The ban entry could not be removed. This may occur if ban_id does not designate
# a valid ban entry.
#
'TOX_ERR_GROUP_MOD_REMOVE_BAN_FAIL_ACTION': 3,
#
# The packet failed to send.
#
'TOX_ERR_GROUP_MOD_REMOVE_BAN_FAIL_SEND': 4
}
TOX_GROUP_MOD_EVENT = {
#
# A peer has been kicked from the group.
#
'KICK': 0,
#
# A peer has been banned from the group.
#
'BAN': 1,
#
# A peer as been given the observer role.
#
'OBSERVER': 2,
#
# A peer has been given the user role.
#
'USER': 3,
#
# A peer has been given the moderator role.
#
'MODERATOR': 4,
}
TOX_ERR_GROUP_BAN_QUERY = {
#
# The function returned successfully.
#
'TOX_ERR_GROUP_BAN_QUERY_OK': 0,
#
# The group number passed did not designate a valid group.
#
'TOX_ERR_GROUP_BAN_QUERY_GROUP_NOT_FOUND': 1,
#
# The ban_id does not designate a valid ban list entry.
#
'TOX_ERR_GROUP_BAN_QUERY_BAD_ID': 2,
}
TOX_GROUP_BAN_TYPE = {
'IP_PORT': 0,
'PUBLIC_KEY': 1,
'NICK': 2
}
TOX_PUBLIC_KEY_SIZE = 32
TOX_ADDRESS_SIZE = TOX_PUBLIC_KEY_SIZE + 6
TOX_MAX_FRIEND_REQUEST_LENGTH = 1016
TOX_MAX_MESSAGE_LENGTH = 1372
TOX_GROUP_MAX_TOPIC_LENGTH = 512
TOX_GROUP_MAX_PART_LENGTH = 128
TOX_GROUP_MAX_GROUP_NAME_LENGTH = 48
TOX_GROUP_MAX_PASSWORD_SIZE = 32
TOX_GROUP_CHAT_ID_SIZE = 32
TOX_GROUP_PEER_PUBLIC_KEY_SIZE = 32
TOX_MAX_NAME_LENGTH = 128
TOX_MAX_STATUS_MESSAGE_LENGTH = 1007
TOX_SECRET_KEY_SIZE = 32
TOX_FILE_ID_LENGTH = 32
TOX_HASH_LENGTH = 32
TOX_MAX_CUSTOM_PACKET_SIZE = 1373

View File

@ -1,82 +0,0 @@
# -*- mode: python; indent-tabs-mode: nil; py-indent-offset: 4; coding: utf-8 -*-
try:
from wrapper import libtox
from wrapper.toxencryptsave_enums_and_consts import *
except:
import libtox
from toxencryptsave_enums_and_consts import *
from ctypes import (ArgumentError, byref, c_bool, c_char_p, c_int, c_size_t,
create_string_buffer)
class ToxEncryptSave:
def __init__(self):
self.libtoxencryptsave = libtox.LibToxEncryptSave()
def is_data_encrypted(self, data):
"""
Checks if given data is encrypted
"""
func = self.libtoxencryptsave.tox_is_data_encrypted
func.restype = c_bool
result = func(c_char_p(bytes(data)))
return result
def pass_encrypt(self, data, password):
"""
Encrypts the given data with the given password.
:return: output array
"""
out = create_string_buffer(len(data) + TOX_PASS_ENCRYPTION_EXTRA_LENGTH)
tox_err_encryption = c_int()
self.libtoxencryptsave.tox_pass_encrypt(c_char_p(data),
c_size_t(len(data)),
c_char_p(bytes(password, 'utf-8')),
c_size_t(len(password)),
out,
byref(tox_err_encryption))
tox_err_encryption = tox_err_encryption.value
if tox_err_encryption == TOX_ERR_ENCRYPTION['OK']:
return out[:]
elif tox_err_encryption == TOX_ERR_ENCRYPTION['NULL']:
raise ArgumentError('Some input data, or maybe the output pointer, was null.')
elif tox_err_encryption == TOX_ERR_ENCRYPTION['KEY_DERIVATION_FAILED']:
raise RuntimeError('The crypto lib was unable to derive a key from the given passphrase, which is usually a'
' lack of memory issue. The functions accepting keys do not produce this error.')
elif tox_err_encryption == TOX_ERR_ENCRYPTION['FAILED']:
raise RuntimeError('The encryption itself failed.')
def pass_decrypt(self, data, password):
"""
Decrypts the given data with the given password.
:return: output array
"""
out = create_string_buffer(len(data) - TOX_PASS_ENCRYPTION_EXTRA_LENGTH)
tox_err_decryption = c_int()
self.libtoxencryptsave.tox_pass_decrypt(c_char_p(bytes(data)),
c_size_t(len(data)),
c_char_p(bytes(password, 'utf-8')),
c_size_t(len(password)),
out,
byref(tox_err_decryption))
tox_err_decryption = tox_err_decryption.value
if tox_err_decryption == TOX_ERR_DECRYPTION['OK']:
return out[:]
elif tox_err_decryption == TOX_ERR_DECRYPTION['NULL']:
raise ArgumentError('Some input data, or maybe the output pointer, was null.')
elif tox_err_decryption == TOX_ERR_DECRYPTION['INVALID_LENGTH']:
raise ArgumentError('The input data was shorter than TOX_PASS_ENCRYPTION_EXTRA_LENGTH bytes')
elif tox_err_decryption == TOX_ERR_DECRYPTION['BAD_FORMAT']:
raise ArgumentError('The input data is missing the magic number (i.e. wasn\'t created by this module, or is'
' corrupted)')
elif tox_err_decryption == TOX_ERR_DECRYPTION['KEY_DERIVATION_FAILED']:
raise RuntimeError('The crypto lib was unable to derive a key from the given passphrase, which is usually a'
' lack of memory issue. The functions accepting keys do not produce this error.')
elif tox_err_decryption == TOX_ERR_DECRYPTION['FAILED']:
raise RuntimeError('The encrypted byte array could not be decrypted. Either the data was corrupt or the '
'password/key was incorrect.')

View File

@ -1,29 +0,0 @@
TOX_ERR_ENCRYPTION = {
# The function returned successfully.
'OK': 0,
# Some input data, or maybe the output pointer, was null.
'NULL': 1,
# The crypto lib was unable to derive a key from the given passphrase, which is usually a lack of memory issue. The
# functions accepting keys do not produce this error.
'KEY_DERIVATION_FAILED': 2,
# The encryption itself failed.
'FAILED': 3
}
TOX_ERR_DECRYPTION = {
# The function returned successfully.
'OK': 0,
# Some input data, or maybe the output pointer, was null.
'NULL': 1,
# The input data was shorter than TOX_PASS_ENCRYPTION_EXTRA_LENGTH bytes
'INVALID_LENGTH': 2,
# The input data is missing the magic number (i.e. wasn't created by this module, or is corrupted)
'BAD_FORMAT': 3,
# The crypto lib was unable to derive a key from the given passphrase, which is usually a lack of memory issue. The
# functions accepting keys do not produce this error.
'KEY_DERIVATION_FAILED': 4,
# The encrypted byte array could not be decrypted. Either the data was corrupt or the password/key was incorrect.
'FAILED': 5,
}
TOX_PASS_ENCRYPTION_EXTRA_LENGTH = 80

Some files were not shown because too many files have changed in this diff Show More