Compare commits
37 Commits
f1d8ce105c
...
develop
Author | SHA1 | Date | |
---|---|---|---|
8c9d53903f | |||
ef68b7e2e2 | |||
affaa3814b | |||
9c1014ee5e | |||
ec79c0d6ae | |||
2717f4f6e5 | |||
f7e260a355 | |||
d9ef18631d | |||
76ad2ccd44 | |||
dda2a9147a | |||
d936663591 | |||
ac6999924f | |||
31bed51455 | |||
dd8ed70958 | |||
d1c8d445bc | |||
4d2d4034f9 | |||
c70c501fdd | |||
e778108834 | |||
1c56b5b25a | |||
ea454e27a1 | |||
f62e28f5b4 | |||
7cebe9cd9f | |||
3ce822fc27 | |||
e4b1b9c4d8 | |||
68f28fdac5 | |||
99136cd4e3 | |||
65d593cd20 | |||
9f32dc3f8a | |||
48efb5a44e | |||
ba013b6a81 | |||
4109c822b3 | |||
4e77ddc2de | |||
dcde8e3d1e | |||
948335c8a0 | |||
0b1eaa1391 | |||
424e15b31c | |||
db37d29dc8 |
43
.github/workflows/ci.yml
vendored
Normal file
43
.github/workflows/ci.yml
vendored
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
name: CI
|
||||||
|
|
||||||
|
on:
|
||||||
|
- push
|
||||||
|
- pull_request
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
|
||||||
|
build:
|
||||||
|
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
python-version:
|
||||||
|
- "3.7"
|
||||||
|
- "3.8"
|
||||||
|
- "3.9"
|
||||||
|
- "3.10"
|
||||||
|
|
||||||
|
name: Python ${{ matrix.python-version }}
|
||||||
|
runs-on: ubuntu-20.04
|
||||||
|
|
||||||
|
steps:
|
||||||
|
|
||||||
|
- uses: actions/checkout@v2
|
||||||
|
|
||||||
|
- name: Set up Python ${{ matrix.python-version }}
|
||||||
|
uses: actions/setup-python@v2
|
||||||
|
with:
|
||||||
|
python-version: ${{ matrix.python-version }}
|
||||||
|
|
||||||
|
- name: Install dependencies
|
||||||
|
run: |
|
||||||
|
pip install -r requirements.txt
|
||||||
|
pip install bandit flake8 pylint
|
||||||
|
|
||||||
|
- name: Lint with flake8
|
||||||
|
run: make flake8
|
||||||
|
|
||||||
|
# - name: Lint with pylint
|
||||||
|
# run: make pylint
|
||||||
|
|
||||||
|
- name: Lint with bandit
|
||||||
|
run: make bandit
|
17
.gitignore
vendored
17
.gitignore
vendored
@ -1,15 +1,27 @@
|
|||||||
|
.pylint.err
|
||||||
|
.pylint.out
|
||||||
*.pyc
|
*.pyc
|
||||||
*.pyo
|
*.pyo
|
||||||
|
|
||||||
|
*.zip
|
||||||
|
*.bak
|
||||||
|
*.lis
|
||||||
|
*.dst
|
||||||
|
*.so
|
||||||
|
|
||||||
toxygen/toxcore
|
toxygen/toxcore
|
||||||
tests/tests
|
tests/tests
|
||||||
tests/libs
|
toxygen/libs
|
||||||
tests/.cache
|
tests/.cache
|
||||||
tests/__pycache__
|
tests/__pycache__
|
||||||
tests/avatars
|
tests/avatars
|
||||||
toxygen/libs
|
toxygen/libs
|
||||||
.idea
|
.idea
|
||||||
*~
|
*~
|
||||||
|
#*
|
||||||
*.iml
|
*.iml
|
||||||
|
*.junk
|
||||||
|
|
||||||
*.so
|
*.so
|
||||||
*.log
|
*.log
|
||||||
toxygen/build
|
toxygen/build
|
||||||
@ -25,4 +37,5 @@ Toxygen.egg-info
|
|||||||
*.tox
|
*.tox
|
||||||
.cache
|
.cache
|
||||||
*.db
|
*.db
|
||||||
|
*~
|
||||||
|
Makefile
|
||||||
|
23
.pre-commit-config.yaml
Normal file
23
.pre-commit-config.yaml
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
# -*- mode: yaml; indent-tabs-mode: nil; tab-width: 2; coding: utf-8-unix -*-
|
||||||
|
---
|
||||||
|
|
||||||
|
default_language_version:
|
||||||
|
python: python3.11
|
||||||
|
default_stages: [pre-commit]
|
||||||
|
fail_fast: true
|
||||||
|
repos:
|
||||||
|
- repo: local
|
||||||
|
hooks:
|
||||||
|
- id: pylint
|
||||||
|
name: pylint
|
||||||
|
entry: env PYTHONPATH=/mnt/o/var/local/src/toxygen.git/toxygen toxcore_pylint.bash
|
||||||
|
language: system
|
||||||
|
types: [python]
|
||||||
|
args:
|
||||||
|
[
|
||||||
|
"--source-roots=/mnt/o/var/local/src/toxygen.git/toxygen",
|
||||||
|
"-rn", # Only display messages
|
||||||
|
"-sn", # Don't display the score
|
||||||
|
"--rcfile=/usr/local/etc/testforge/pylint.rc", # Link to your config file
|
||||||
|
"-E"
|
||||||
|
]
|
4
.pylintrc
Normal file
4
.pylintrc
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
[pre-commit-hook]
|
||||||
|
command=env PYTHONPATH=/mnt/o/var/local/src/toxygen.git/toxygen /usr/local/bin/toxcore_pylint.bash
|
||||||
|
params= -E --exit-zero
|
||||||
|
limit=8
|
8
.rsync.sh
Normal file
8
.rsync.sh
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
#find * -name \*.py | xargs grep -l '[ ]*$' | xargs sed -i -e 's/[ ]*$//'
|
||||||
|
rsync "$@" -vaxL --include \*.py \
|
||||||
|
--exclude Toxygen.egg-info --exclude build \
|
||||||
|
--exclude \*.pyc --exclude .pyl\* --exclude \*.so --exclude \*~ \
|
||||||
|
--exclude __pycache__ --exclude \*.egg-info --exclude \*.new \
|
||||||
|
./ ../toxygen.git/|grep -v /$
|
109
README.md
109
README.md
@ -1,13 +1,17 @@
|
|||||||
# Toxygen
|
# Toxygen
|
||||||
|
|
||||||
Toxygen is powerful cross-platform [Tox](https://tox.chat/) client written in pure Python3.
|
Toxygen is powerful cross-platform [Tox](https://tox.chat/) client
|
||||||
|
for Tox and IRC/weechat written in pure Python3.
|
||||||
|
|
||||||
### [Install](/docs/install.md) - [Contribute](/docs/contributing.md) - [Plugins](/docs/plugins.md) - [Compile](/docs/compile.md) - [Contact](/docs/contact.md)
|
### [Install](/docs/install.md) - [Contribute](/docs/contributing.md) - [Plugins](/docs/plugins.md) - [Compile](/docs/compile.md) - [Contact](/docs/contact.md)
|
||||||
|
|
||||||
### Supported OS: Linux and Windows
|
### Supported OS: Linux and Windows (only Linux is tested at the moment)
|
||||||
|
|
||||||
### Features:
|
### Features:
|
||||||
|
|
||||||
|
- PyQt5, PyQt6, and maybe PySide2, PySide6 via qtpy
|
||||||
|
- IRC via weechat /relay
|
||||||
|
- NGC groups
|
||||||
- 1v1 messages
|
- 1v1 messages
|
||||||
- File transfers
|
- File transfers
|
||||||
- Audio calls
|
- Audio calls
|
||||||
@ -19,14 +23,13 @@ Toxygen is powerful cross-platform [Tox](https://tox.chat/) client written in pu
|
|||||||
- Emoticons
|
- Emoticons
|
||||||
- Stickers
|
- Stickers
|
||||||
- Screenshots
|
- Screenshots
|
||||||
- Name lookups (toxme.io support)
|
|
||||||
- Save file encryption
|
- Save file encryption
|
||||||
- Profile import and export
|
- Profile import and export
|
||||||
- Faux offline messaging
|
- Faux offline messaging
|
||||||
- Faux offline file transfers
|
- Faux offline file transfers
|
||||||
- Inline images
|
- Inline images
|
||||||
- Message splitting
|
- Message splitting
|
||||||
- Proxy support
|
- Proxy support - runs over tor, without DNS leaks
|
||||||
- Avatars
|
- Avatars
|
||||||
- Multiprofile
|
- Multiprofile
|
||||||
- Multilingual
|
- Multilingual
|
||||||
@ -37,30 +40,108 @@ Toxygen is powerful cross-platform [Tox](https://tox.chat/) client written in pu
|
|||||||
- Changing nospam
|
- Changing nospam
|
||||||
- File resuming
|
- File resuming
|
||||||
- Read receipts
|
- Read receipts
|
||||||
- NGC groups
|
- uses gevent
|
||||||
|
|
||||||
### Screenshots
|
### Screenshots
|
||||||
*Toxygen on Ubuntu and Windows*
|
*Toxygen on Ubuntu and Windows*
|
||||||

|

|
||||||

|

|
||||||
|
|
||||||
|
Windows was working but is not currently being tested. AV is working
|
||||||
|
but the video is garbled: we're unsure of naming the AV devices
|
||||||
|
from the commandline. We need to get a working echobot that supports SOCKS5;
|
||||||
|
we were working on one in https://git.plastiras.org/emdee/toxygen_wrapper
|
||||||
|
|
||||||
## Forked
|
## Forked
|
||||||
|
|
||||||
This hard-forked from https://github.com/toxygen-project/toxygen
|
This hard-forked from the dead https://github.com/toxygen-project/toxygen
|
||||||
```next_gen``` branch.
|
```next_gen``` branch.
|
||||||
|
|
||||||
https://git.plastiras.org/emdee/toxygen_wrapper needs packaging
|
|
||||||
is making a dependency. Just download it and copy the two directories
|
|
||||||
```wrapper``` and ```wrapper_tests``` into ```toxygen/toxygen```.
|
|
||||||
|
|
||||||
See ToDo.md to the current ToDo list.
|
See ToDo.md to the current ToDo list.
|
||||||
|
|
||||||
If you install https://github.com/weechat/qweechat
|
## IRC Weechat
|
||||||
you can have IRC and jabber in a window too. Start
|
|
||||||
[weechat](https://github.com/weechat/weechat) and
|
You can have a [weechat](https://github.com/weechat/qweechat)
|
||||||
|
console so that you can have IRC and jabber in a window as well as Tox.
|
||||||
|
There's a copy of qweechat in https://git.plastiras.org/emdee/qweechat
|
||||||
|
that you must install first, which was backported to PyQt5 now to qtpy
|
||||||
|
(PyQt5 PyQt6 and PySide2 and PySide6) and integrated into toxygen.
|
||||||
|
Follow the normal instructions for adding a ```relay``` to
|
||||||
|
[weechat](https://github.com/weechat/weechat)
|
||||||
```
|
```
|
||||||
/relay weechat 9000 password
|
/relay add weechat 9000
|
||||||
|
/relay start weechat
|
||||||
```
|
```
|
||||||
|
or
|
||||||
|
```
|
||||||
|
weechat -r '/relay add weechat 9000;/relay start weechat'
|
||||||
|
```
|
||||||
|
and use the Plugins -> Weechat Console to start weechat under Toxygen.
|
||||||
|
Then use the File/Connect menu item of the Console to connect to weechat.
|
||||||
|
|
||||||
|
Weechat has a Jabber plugin to enable XMPP:
|
||||||
|
```
|
||||||
|
/python load jabber.el
|
||||||
|
/help jabber
|
||||||
|
```
|
||||||
|
so you can have Tox, IRC and XMPP in the same application! See docs/ToxygenWeechat.md
|
||||||
|
|
||||||
|
## Install
|
||||||
|
|
||||||
|
To install read the requirements.txt and look at the comments; there
|
||||||
|
are things that need installing by hand or decisions to be made
|
||||||
|
on supported alternatives.
|
||||||
|
|
||||||
|
https://git.plastiras.org/emdee/toxygen_wrapper needs installing as it is a
|
||||||
|
dependency. Just download and install it from
|
||||||
|
https://git.plastiras.org/emdee/toxygen_wrapper The same with
|
||||||
|
https://git.plastiras.org/emdee/qweechat
|
||||||
|
|
||||||
|
This is being ported to Qt6 using qtpy https://github.com/spyder-ide/qtpy
|
||||||
|
It now runs on PyQt5 and PyQt6, and may run on PySide2 and PySide6 - YMMV.
|
||||||
|
You will be able to choose between them by setting the environment variable
|
||||||
|
```QT_API``` to one of: ```pyqt5 pyqt6 pyside2 pyside6```.
|
||||||
|
It's currently tested mainly on PyQt5.
|
||||||
|
|
||||||
|
To install it, look in the Makefile for the install target and type
|
||||||
|
```
|
||||||
|
make install
|
||||||
|
```
|
||||||
|
You should set the PIP_EXE_MSYS and PYTHON_EXE_MSYS variables and it does
|
||||||
|
```
|
||||||
|
${PIP_EXE_MSYS} --python ${PYTHON_EXE_MSYS} install \
|
||||||
|
--no-deps \
|
||||||
|
--target ${PREFIX}/lib/python${PYTHON_MINOR}/site-packages/ \
|
||||||
|
--upgrade .
|
||||||
|
```
|
||||||
|
and installs into PREFIX which is usually /usr/local
|
||||||
|
|
||||||
|
## Updates
|
||||||
|
|
||||||
|
Up-to-date code is on https://git.plastiras.org/emdee/toxygen
|
||||||
|
|
||||||
|
Tox works over Tor, and the c-toxcore library can leak DNS requests
|
||||||
|
due to a 6-year old known security issue:
|
||||||
|
https://github.com/TokTok/c-toxcore/issues/469 but toxygen looksup
|
||||||
|
addresses before calling c-toxcore. This also allows us to use onion
|
||||||
|
addresses in the DHTnodes.json file. Still for anonymous communication
|
||||||
|
we recommend having a TCP and UDP firewall in place.
|
||||||
|
|
||||||
|
Although Tox works with multi-user group chat, there are no checks
|
||||||
|
against impersonation of a screen nickname, so you may not be chatting
|
||||||
|
with the person you think. For the Toxic client, the (closed) issue is:
|
||||||
|
https://github.com/JFreegman/toxic/issues/622#issuecomment-1922116065
|
||||||
|
Solving this might best be done with a solution to MultiDevice q.v.
|
||||||
|
|
||||||
|
The Tox project does not follow semantic versioning of its main structures
|
||||||
|
in C so the project may break the underlying ctypes wrapper at any time;
|
||||||
|
it's not possible to use Tox version numbers to tell what the API will be.
|
||||||
|
The last git version this code was tested with is
|
||||||
|
``1623e3ee5c3a5837a92f959f289fcef18bfa9c959``` of Feb 12 10:06:37 2024.
|
||||||
|
In which case you may need to go into the tox.py file in
|
||||||
|
https://git.plastiras.org/emdee/toxygen_wrapper to fix it yourself.
|
||||||
|
|
||||||
|
## MultiDevice
|
||||||
|
|
||||||
Work on this project is suspended until the
|
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!
|
[MultiDevice](https://git.plastiras.org/emdee/tox_profile/wiki/MultiDevice-Announcements-POC) problem is solved. Fork me!
|
||||||
|
28
ToDo.md
28
ToDo.md
@ -4,14 +4,14 @@
|
|||||||
|
|
||||||
1. There is an agravating bug where new messages are not put in the
|
1. There is an agravating bug where new messages are not put in the
|
||||||
current window, and a messages waiting indicator appears. You have
|
current window, and a messages waiting indicator appears. You have
|
||||||
to focus out of the window and then back in the window.
|
to focus out of the window and then back in the window. this may be
|
||||||
|
fixed already
|
||||||
|
|
||||||
|
2. The tray icon is flaky and has been disabled - look in app.py
|
||||||
|
for bSHOW_TRAY
|
||||||
|
|
||||||
## Fix history
|
## Fix history
|
||||||
|
|
||||||
The code is in there but it's not working.
|
|
||||||
|
|
||||||
## Fix Audio
|
## Fix Audio
|
||||||
|
|
||||||
The code is in there but it's not working. It looks like audio input
|
The code is in there but it's not working. It looks like audio input
|
||||||
@ -25,7 +25,7 @@ The code is in there but it's not working. I may have broken it
|
|||||||
trying to wire up the ability to set the video device from the command
|
trying to wire up the ability to set the video device from the command
|
||||||
line.
|
line.
|
||||||
|
|
||||||
## Groups
|
## NGC Groups
|
||||||
|
|
||||||
1. peer_id There has been a change of API on a field named
|
1. peer_id There has been a change of API on a field named
|
||||||
```group.peer_id``` The code is broken in places because I have not
|
```group.peer_id``` The code is broken in places because I have not
|
||||||
@ -50,3 +50,21 @@ line.
|
|||||||
|
|
||||||
2. https://git.plastiras.org/emdee/toxygen_wrapper needs packaging
|
2. https://git.plastiras.org/emdee/toxygen_wrapper needs packaging
|
||||||
and making a dependency.
|
and making a dependency.
|
||||||
|
|
||||||
|
## Migration
|
||||||
|
|
||||||
|
Migrate PyQt5 to qtpy - done, but I'm not sure qtpy supports PyQt6.
|
||||||
|
https://github.com/spyder-ide/qtpy/
|
||||||
|
|
||||||
|
Maybe migrate gevent to asyncio, and migrate to
|
||||||
|
[qasync](https://github.com/CabbageDevelopment/qasync)
|
||||||
|
(see https://git.plastiras.org/emdee/phantompy ).
|
||||||
|
|
||||||
|
(Also look at https://pypi.org/project/asyncio-gevent/ but it's dead).
|
||||||
|
|
||||||
|
## Standards
|
||||||
|
|
||||||
|
There's a standard for Tox clients that this has not been tested against:
|
||||||
|
https://tox.gitbooks.io/tox-client-standard/content/general_requirements/general_requirements.html
|
||||||
|
https://github.com/Tox/Tox-Client-Standard
|
||||||
|
|
||||||
|
130
_Bugs/segv.err
Normal file
130
_Bugs/segv.err
Normal 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)]
|
11
_Bugs/tox.abilinski.com.ping
Normal file
11
_Bugs/tox.abilinski.com.ping
Normal 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
|
@ -1,13 +0,0 @@
|
|||||||
FROM ubuntu:16.04
|
|
||||||
|
|
||||||
RUN apt-get update && \
|
|
||||||
apt-get install build-essential libtool autotools-dev automake checkinstall cmake check git yasm libsodium-dev libopus-dev libvpx-dev pkg-config -y && \
|
|
||||||
git clone https://github.com/ingvar1995/toxcore.git --branch=ngc_rebase && \
|
|
||||||
cd toxcore && mkdir _build && cd _build && \
|
|
||||||
cmake .. && make && make install
|
|
||||||
|
|
||||||
RUN apt-get install portaudio19-dev python3-pyqt5 python3-pyaudio python3-pip -y && \
|
|
||||||
pip3 install numpy pydenticon opencv-python pyinstaller
|
|
||||||
|
|
||||||
RUN useradd -ms /bin/bash toxygen
|
|
||||||
USER toxygen
|
|
@ -1,33 +0,0 @@
|
|||||||
#!/usr/bin/env bash
|
|
||||||
|
|
||||||
cd ~
|
|
||||||
git clone https://github.com/toxygen-project/toxygen.git --branch=next_gen
|
|
||||||
cd toxygen/toxygen
|
|
||||||
|
|
||||||
pyinstaller --windowed --icon=images/icon.ico main.py
|
|
||||||
|
|
||||||
cp -r styles dist/main/
|
|
||||||
find . -type f ! -name '*.qss' -delete
|
|
||||||
cp -r plugins dist/main/
|
|
||||||
mkdir -p dist/main/ui/views
|
|
||||||
cp -r ui/views dist/main/ui/
|
|
||||||
cp -r sounds dist/main/
|
|
||||||
cp -r smileys dist/main/
|
|
||||||
cp -r stickers dist/main/
|
|
||||||
cp -r bootstrap dist/main/
|
|
||||||
find . -type f ! -name '*.json' -delete
|
|
||||||
cp -r images dist/main/
|
|
||||||
cp -r translations dist/main/
|
|
||||||
find . -name "*.ts" -type f -delete
|
|
||||||
|
|
||||||
cd dist
|
|
||||||
mv main toxygen
|
|
||||||
cd toxygen
|
|
||||||
mv main toxygen
|
|
||||||
wget -O updater https://github.com/toxygen-project/toxygen_updater/releases/download/v0.1/toxygen_updater_linux_64
|
|
||||||
echo "[Paths]" >> qt.conf
|
|
||||||
echo "Prefix = PyQt5/Qt" >> qt.conf
|
|
||||||
cd ..
|
|
||||||
|
|
||||||
tar -zcvf toxygen_linux_64.tar.gz toxygen > /dev/null
|
|
||||||
rm -rf toxygen
|
|
171
docs/ToxygenWeechat.md
Normal file
171
docs/ToxygenWeechat.md
Normal file
@ -0,0 +1,171 @@
|
|||||||
|
## Toxygen Weechat
|
||||||
|
|
||||||
|
You can have a [weechat](https://github.com/weechat/qweechat)
|
||||||
|
console so that you can have IRC and jabber in a window as well as Tox.
|
||||||
|
There's a copy of qweechat in ```thirdparty/qweechat``` backported to
|
||||||
|
PyQt5 and integrated into toxygen. Follow the normal instructions for
|
||||||
|
adding a ```relay``` to [weechat](https://github.com/weechat/weechat)
|
||||||
|
```
|
||||||
|
/relay add ipv4.ssl.weechat 9000
|
||||||
|
/relay start ipv4.ssl.weechat
|
||||||
|
```
|
||||||
|
or
|
||||||
|
```
|
||||||
|
/set relay.network.ipv6 off
|
||||||
|
/set relay.network.password password
|
||||||
|
/relay add weechat 9000
|
||||||
|
/relay start weechat
|
||||||
|
```
|
||||||
|
and use the Plugins/Weechat Console to start weechat under Toxygen.
|
||||||
|
Then use the File/Connect menu item of the Console to connect to weechat.
|
||||||
|
|
||||||
|
Weechat has a Jabber plugin to enable XMPP:
|
||||||
|
```
|
||||||
|
/python load jabber.el
|
||||||
|
/help jabber
|
||||||
|
```
|
||||||
|
so you can have Tox, IRC and XMPP in the same application!
|
||||||
|
|
||||||
|
### Creating servers for IRC over Tor
|
||||||
|
|
||||||
|
Create a proxy called tor
|
||||||
|
```
|
||||||
|
/proxy add tor socks5 127.0.0.1 9050
|
||||||
|
```
|
||||||
|
|
||||||
|
It should now show up in the list of proxies.
|
||||||
|
```
|
||||||
|
/proxy list
|
||||||
|
```
|
||||||
|
|
||||||
|
```
|
||||||
|
/nick NickName
|
||||||
|
```
|
||||||
|
|
||||||
|
## TLS certificates
|
||||||
|
|
||||||
|
[Create a Self-signed Certificate](https://www.oftc.net/NickServ/CertFP/)
|
||||||
|
|
||||||
|
Choose a NickName you will identify as.
|
||||||
|
|
||||||
|
Create a directory for your certificates ~/.config/weechat/ssl/
|
||||||
|
and make a subdirectory for each server ~/.config/weechat/ssl/irc.oftc.net/
|
||||||
|
|
||||||
|
Change to the server directory and use openssl to make a keypair and answer the questions:
|
||||||
|
```
|
||||||
|
openssl req -nodes -newkey rsa:2048 -keyout NickName.key -x509 -days 3650 -out NickName.cer
|
||||||
|
chmod 400 NickName.key
|
||||||
|
```
|
||||||
|
We now combine certificate and key to a single file NickName.pem
|
||||||
|
```
|
||||||
|
cat NickName.cer NickName.key > NickName.pem
|
||||||
|
chmod 400 NickName.pem
|
||||||
|
```
|
||||||
|
|
||||||
|
Do this for each server you want to connect to, or just use one for all of them.
|
||||||
|
|
||||||
|
### Libera TokTok channel
|
||||||
|
|
||||||
|
The main discussion forum for Tox is the #TokTok channel on libera.
|
||||||
|
|
||||||
|
https://mox.sh/sysadmin/secure-irc-connection-to-freenode-with-tor-and-weechat/
|
||||||
|
We have to create an account without Tor, this is a requirement to use TOR:
|
||||||
|
Connect to irc.libera.chat without Tor and register
|
||||||
|
```
|
||||||
|
/msg NickServ identify NickName password
|
||||||
|
/msg NickServ REGISTER mypassword mycoolemail@example.com
|
||||||
|
/msg NickServ SET PRIVATE ON
|
||||||
|
```
|
||||||
|
You'll get an email with a registration code.
|
||||||
|
Confirm registration after getting the mail with the code:
|
||||||
|
```
|
||||||
|
/msg NickServ VERIFY REGISTER NickName code1235678
|
||||||
|
```
|
||||||
|
|
||||||
|
Libera has an onion server so we can map an address in tor. Add this
|
||||||
|
to your /etc/tor/torrc
|
||||||
|
```
|
||||||
|
MapAddress palladium.libera.chat libera75jm6of4wxpxt4aynol3xjmbtxgfyjpu34ss4d7r7q2v5zrpyd.onion
|
||||||
|
```
|
||||||
|
Or without the MapAddress just use
|
||||||
|
libera75jm6of4wxpxt4aynol3xjmbtxgfyjpu34ss4d7r7q2v5zrpyd.onion
|
||||||
|
as the server address below, but set tls_verify to off.
|
||||||
|
|
||||||
|
Define the server in weechat
|
||||||
|
https://www.weechat.org/files/doc/stable/weechat_user.en.html#irc_sasl_authentication
|
||||||
|
```
|
||||||
|
/server remove libera
|
||||||
|
/server add libera palladium.libera.chat/6697 -tls -tls_verify
|
||||||
|
/set irc.server.libera.ipv6 off
|
||||||
|
/set irc.server.libera.proxy tor
|
||||||
|
/set irc.server.libera.username NickName
|
||||||
|
/set irc.server.libera.password password
|
||||||
|
/set irc.server.libera.nicks NickName
|
||||||
|
/set irc.server.libera.tls on
|
||||||
|
/set irc.server.libera.tls_cert "${weechat_config_dir}/ssl/libera.chat/NickName.pem"
|
||||||
|
```
|
||||||
|
|
||||||
|
```
|
||||||
|
/set irc.server.libera.sasl_mechanism ecdsa-nist256p-challenge
|
||||||
|
/set irc.server.libera.sasl_username "NickName"
|
||||||
|
/set irc.server.libera.sasl_key "${weechat_config_dir}/ssl/libera.chat/NickName.pem"
|
||||||
|
```
|
||||||
|
|
||||||
|
Disconnect and connect back to the server.
|
||||||
|
```
|
||||||
|
/disconnect libera
|
||||||
|
/connect libera
|
||||||
|
```
|
||||||
|
|
||||||
|
/msg nickserv identify password NickName
|
||||||
|
|
||||||
|
|
||||||
|
### oftc.net
|
||||||
|
|
||||||
|
To use oftc.net over tor, you need to authenticate by SSL certificates.
|
||||||
|
|
||||||
|
|
||||||
|
Define the server in weechat
|
||||||
|
```
|
||||||
|
/server remove irc.oftc.net
|
||||||
|
/server add OFTC irc.oftc.net/6697 -tls -tls_verify
|
||||||
|
/set irc.server.OFTC.ipv6 off
|
||||||
|
/set irc.server.OFTC.proxy tor
|
||||||
|
/set irc.server.OFTC.username NickName
|
||||||
|
/set irc.server.OFTC.nicks NickName
|
||||||
|
/set irc.server.OFTC.tls on
|
||||||
|
/set irc.server.OFTC.tls_cert "${weechat_config_dir}/ssl/irc.oftc.chat/NickName.pem"
|
||||||
|
|
||||||
|
# Disconnect and connect back to the server.
|
||||||
|
/disconnect OFTC
|
||||||
|
/connect OFTC
|
||||||
|
```
|
||||||
|
You must be identified in order to validate using certs
|
||||||
|
```
|
||||||
|
/msg nickserv identify password NickName
|
||||||
|
```
|
||||||
|
To allow NickServ to identify you based on this certificate you need
|
||||||
|
to associate the certificate fingerprint with your nick. To do this
|
||||||
|
issue the command cert add to Nickserv (try /msg nickserv helpcert).
|
||||||
|
```
|
||||||
|
/msg nickserv cert add
|
||||||
|
```
|
||||||
|
|
||||||
|
### Privacy
|
||||||
|
|
||||||
|
[Add somes settings bellow to weechat](https://szorfein.github.io/weechat/tor/configure-weechat/).
|
||||||
|
Detail from [faq](https://weechat.org/files/doc/weechat_faq.en.html#security).
|
||||||
|
|
||||||
|
```
|
||||||
|
/set irc.server_default.msg_part ""
|
||||||
|
/set irc.server_default.msg_quit ""
|
||||||
|
/set irc.ctcp.clientinfo ""
|
||||||
|
/set irc.ctcp.finger ""
|
||||||
|
/set irc.ctcp.source ""
|
||||||
|
/set irc.ctcp.time ""
|
||||||
|
/set irc.ctcp.userinfo ""
|
||||||
|
/set irc.ctcp.version ""
|
||||||
|
/set irc.ctcp.ping ""
|
||||||
|
/plugin unload xfer
|
||||||
|
/set weechat.plugin.autoload "*,!xfer"
|
||||||
|
```
|
@ -21,8 +21,8 @@ Note: 32-bit Python isn't supported due to bug with videocalls. It is strictly r
|
|||||||
3. Install PyAudio: ``pip install pyaudio``
|
3. Install PyAudio: ``pip install pyaudio``
|
||||||
4. Install numpy: ``pip install numpy``
|
4. Install numpy: ``pip install numpy``
|
||||||
5. Install OpenCV: ``pip install opencv-python``
|
5. Install OpenCV: ``pip install opencv-python``
|
||||||
6. [Download toxygen](https://github.com/toxygen-project/toxygen/archive/master.zip)
|
6. git clone --depth=1 https://git.plastiras.org/emdee/toxygen/
|
||||||
7. Unpack archive
|
7. I don't know
|
||||||
8. Download latest libtox.dll build, download latest libsodium.a build, put it into \toxygen\libs\
|
8. Download latest libtox.dll build, download latest libsodium.a build, put it into \toxygen\libs\
|
||||||
9. Run \toxygen\main.py.
|
9. Run \toxygen\main.py.
|
||||||
|
|
||||||
@ -30,15 +30,22 @@ Note: 32-bit Python isn't supported due to bug with videocalls. It is strictly r
|
|||||||
|
|
||||||
1. Install latest Python3:
|
1. Install latest Python3:
|
||||||
``sudo apt-get install python3``
|
``sudo apt-get install python3``
|
||||||
2. Install PyQt5: ``sudo apt-get install python3-pyqt5`` or ``sudo pip3 install pyqt5``
|
2. Install PyQt5: ``sudo apt-get install python3-pyqt5``
|
||||||
3. Install [toxcore](https://github.com/TokTok/c-toxcore) with toxav support)
|
3. Install [toxcore](https://github.com/TokTok/c-toxcore) with toxav support)
|
||||||
4. Install PyAudio:
|
4. Install PyAudio: ``sudo apt-get install portaudio19-dev python3-pyaudio`` (or ``sudo pip3 install pyaudio``)
|
||||||
``sudo apt-get install portaudio19-dev`` and ``sudo apt-get install python3-pyaudio`` (or ``sudo pip3 install pyaudio``)
|
5. Install toxygen_wrapper https://git.plastiras.org/emdee/toxygen_wrapper
|
||||||
5. Install NumPy: ``sudo pip3 install numpy``
|
6. Install the rest of the requirements: ``sudo pip3 install -m requirements.txt``
|
||||||
6. Install [OpenCV](http://docs.opencv.org/trunk/d7/d9f/tutorial_linux_install.html) or via ``sudo pip3 install opencv-python``
|
7. git clone --depth=1 [toxygen](https://git.plastiras.org/emdee/toxygen/)
|
||||||
7. [Download toxygen](https://git.plastiras.org/emdee/toxygen/)
|
8. Look in the Makefile for the install target and type
|
||||||
8. Unpack archive
|
``
|
||||||
|
make install
|
||||||
|
``
|
||||||
|
You should set the PIP_EXE_MSYS and PYTHON_EXE_MSYS variables and it does
|
||||||
|
``
|
||||||
|
${PIP_EXE_MSYS} --python ${PYTHON_EXE_MSYS} install \
|
||||||
|
--target ${PREFIX}/lib/python${PYTHON_MINOR}/site-packages/ \
|
||||||
|
--upgrade .
|
||||||
|
``
|
||||||
9. Run app:
|
9. Run app:
|
||||||
``python3 main.py``
|
``python3 ${PREFIX}/lib/python${PYTHON_MINOR}/site-packages/bin/toxygen``
|
||||||
|
|
||||||
Optional: install toxygen using setup.py: ``python3 setup.py install``
|
|
||||||
|
@ -53,5 +53,5 @@ Plugin's methods MUST NOT raise exceptions.
|
|||||||
|
|
||||||
# Examples
|
# Examples
|
||||||
|
|
||||||
You can find examples in [official repo](https://github.com/toxygen-project/toxygen_plugins)
|
You can find examples in [official repo](https://git.plastiras.org/emdee/toxygen_plugins)
|
||||||
|
|
||||||
|
70
docs/todo.md
Normal file
70
docs/todo.md
Normal file
@ -0,0 +1,70 @@
|
|||||||
|
# Toxygen ToDo List
|
||||||
|
|
||||||
|
## Bugs
|
||||||
|
|
||||||
|
1. There is an agravating bug where new messages are not put in the
|
||||||
|
current window, and a messages waiting indicator appears. You have
|
||||||
|
to focus out of the window and then back in the window. this may be
|
||||||
|
fixed already
|
||||||
|
|
||||||
|
2. The tray icon is flaky and has been disabled - look in app.py
|
||||||
|
for bSHOW_TRAY
|
||||||
|
|
||||||
|
## Fix history
|
||||||
|
|
||||||
|
## Fix Audio
|
||||||
|
|
||||||
|
The code is in there but it's not working. It looks like audio input
|
||||||
|
is working but not output. The code is all in there; I may have broken
|
||||||
|
it trying to wire up the ability to set the audio device from the
|
||||||
|
command line.
|
||||||
|
|
||||||
|
## Fix Video
|
||||||
|
|
||||||
|
The code is in there but it's not working. I may have broken it
|
||||||
|
trying to wire up the ability to set the video device from the command
|
||||||
|
line.
|
||||||
|
|
||||||
|
## NGC Groups
|
||||||
|
|
||||||
|
1. peer_id There has been a change of API on a field named
|
||||||
|
```group.peer_id``` The code is broken in places because I have not
|
||||||
|
seen the path to change from the old API ro the new one.
|
||||||
|
|
||||||
|
|
||||||
|
## Plugin system
|
||||||
|
|
||||||
|
1. Needs better documentation and checking.
|
||||||
|
|
||||||
|
2. There's something broken in the way some of them plug into Qt menus.
|
||||||
|
|
||||||
|
3. Should the plugins be in toxygen or a separate repo?
|
||||||
|
|
||||||
|
4. There needs to be a uniform way for plugins to wire into callbacks.
|
||||||
|
|
||||||
|
## check toxygen_wrapper
|
||||||
|
|
||||||
|
1. I've broken out toxygen_wrapper to be standalone,
|
||||||
|
https://git.plastiras.org/emdee/toxygen_wrapper but the tox.py
|
||||||
|
needs each call double checking.
|
||||||
|
|
||||||
|
2. https://git.plastiras.org/emdee/toxygen_wrapper needs packaging
|
||||||
|
and making a dependency.
|
||||||
|
|
||||||
|
## Migration
|
||||||
|
|
||||||
|
Migrate PyQt5 to qtpy - done, but I'm not sure qtpy supports PyQt6.
|
||||||
|
https://github.com/spyder-ide/qtpy/
|
||||||
|
|
||||||
|
Maybe migrate gevent to asyncio, and migrate to
|
||||||
|
[qasync](https://github.com/CabbageDevelopment/qasync)
|
||||||
|
(see https://git.plastiras.org/emdee/phantompy ).
|
||||||
|
|
||||||
|
(Also look at https://pypi.org/project/asyncio-gevent/ but it's dead).
|
||||||
|
|
||||||
|
## Standards
|
||||||
|
|
||||||
|
There's a standard for Tox clients that this has not been tested against:
|
||||||
|
https://tox.gitbooks.io/tox-client-standard/content/general_requirements/general_requirements.html
|
||||||
|
https://github.com/Tox/Tox-Client-Standard
|
||||||
|
|
56
pyproject.toml
Normal file
56
pyproject.toml
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
[project]
|
||||||
|
name = "toxygen"
|
||||||
|
description = "examples of using stem"
|
||||||
|
authors = [{ name = "emdee", email = "emdee@spm.plastiras.org" } ]
|
||||||
|
requires-python = ">=3.7"
|
||||||
|
keywords = ["stem", "python3", "tox"]
|
||||||
|
classifiers = [
|
||||||
|
# How mature is this project? Common values are
|
||||||
|
# 3 - Alpha
|
||||||
|
# 4 - Beta
|
||||||
|
# 5 - Production/Stable
|
||||||
|
"Development Status :: 4 - Beta",
|
||||||
|
|
||||||
|
# Indicate who your project is intended for
|
||||||
|
"Intended Audience :: Developers",
|
||||||
|
|
||||||
|
# Specify the Python versions you support here.
|
||||||
|
"Programming Language :: Python :: 3",
|
||||||
|
"License :: OSI Approved",
|
||||||
|
"Operating System :: POSIX :: BSD :: FreeBSD",
|
||||||
|
"Operating System :: POSIX :: Linux",
|
||||||
|
"Programming Language :: Python :: 3 :: Only",
|
||||||
|
"Programming Language :: Python :: 3.6",
|
||||||
|
"Programming Language :: Python :: 3.7",
|
||||||
|
"Programming Language :: Python :: 3.8",
|
||||||
|
"Programming Language :: Python :: 3.9",
|
||||||
|
"Programming Language :: Python :: 3.10",
|
||||||
|
"Programming Language :: Python :: 3.11",
|
||||||
|
"Programming Language :: Python :: Implementation :: CPython",
|
||||||
|
]
|
||||||
|
#
|
||||||
|
dynamic = ["version", "readme", "dependencies"] # cannot be dynamic ['license']
|
||||||
|
|
||||||
|
[project.gui-scripts]
|
||||||
|
toxygen = "toxygen.__main__:main"
|
||||||
|
|
||||||
|
[project.optional-dependencies]
|
||||||
|
weechat = ["weechat"]
|
||||||
|
|
||||||
|
#[project.license]
|
||||||
|
#file = "LICENSE.md"
|
||||||
|
|
||||||
|
[project.urls]
|
||||||
|
repository = "https://git.plastiras.org/emdee/toxygen"
|
||||||
|
|
||||||
|
[build-system]
|
||||||
|
requires = ["setuptools >= 61.0"]
|
||||||
|
build-backend = "setuptools.build_meta"
|
||||||
|
|
||||||
|
[tool.setuptools.dynamic]
|
||||||
|
version = {attr = "toxygen.app.__version__"}
|
||||||
|
readme = {file = ["README.md", "ToDo.txt"]}
|
||||||
|
dependencies = {file = ["requirements.txt"]}
|
||||||
|
|
||||||
|
[tool.setuptools]
|
||||||
|
packages = ["toxygen"]
|
26
requirements.txt
Normal file
26
requirements.txt
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
# the versions are the current ones tested - may work with earlier versions
|
||||||
|
# choose one of PyQt5 PyQt6 PySide2 PySide6
|
||||||
|
# for now PyQt5 and PyQt6 is working, and most of the testing is PyQt5
|
||||||
|
# usually this is installed by your OS package manager and pip may not
|
||||||
|
# detect the right version, so we leave these commented
|
||||||
|
# PyQt5 >= 5.15.10
|
||||||
|
# this is not on pypi yet but is required - get it from
|
||||||
|
# https://git.plastiras.org/emdee/toxygen_wrapper
|
||||||
|
# toxygen_wrapper == 1.0.0
|
||||||
|
QtPy >= 2.4.1
|
||||||
|
PyAudio >= 0.2.13
|
||||||
|
numpy >= 1.26.1
|
||||||
|
opencv_python >= 4.8.0
|
||||||
|
pillow >= 10.2.0
|
||||||
|
gevent >= 23.9.1
|
||||||
|
pydenticon >= 0.3.1
|
||||||
|
greenlet >= 2.0.2
|
||||||
|
sounddevice >= 0.3.15
|
||||||
|
# this is optional
|
||||||
|
coloredlogs >= 15.0.1
|
||||||
|
# this is optional
|
||||||
|
# qtconsole >= 5.4.3
|
||||||
|
# this is not on pypi yet but is optional for qweechat - get it from
|
||||||
|
# https://git.plastiras.org/emdee/qweechat
|
||||||
|
# qweechat_wrapper == 0.0.1
|
||||||
|
|
54
setup.cfg
Normal file
54
setup.cfg
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
[metadata]
|
||||||
|
classifiers =
|
||||||
|
License :: OSI Approved
|
||||||
|
License :: OSI Approved :: BSD 1-clause
|
||||||
|
Intended Audience :: Web Developers
|
||||||
|
Operating System :: Microsoft :: Windows
|
||||||
|
Operating System :: POSIX :: BSD :: FreeBSD
|
||||||
|
Operating System :: POSIX :: Linux
|
||||||
|
Programming Language :: Python :: 3 :: Only
|
||||||
|
Programming Language :: Python :: 3.6
|
||||||
|
Programming Language :: Python :: 3.7
|
||||||
|
Programming Language :: Python :: 3.8
|
||||||
|
Programming Language :: Python :: 3.9
|
||||||
|
Programming Language :: Python :: 3.10
|
||||||
|
Programming Language :: Python :: 3.11
|
||||||
|
Programming Language :: Python :: Implementation :: CPython
|
||||||
|
|
||||||
|
[options]
|
||||||
|
zip_safe = false
|
||||||
|
python_requires = ~=3.7
|
||||||
|
include_package_data =
|
||||||
|
"*" = ["*.ui", "*.txt", "*.png", "*.ico", "*.gif", "*.wav"]
|
||||||
|
|
||||||
|
[options.entry_points]
|
||||||
|
console_scripts =
|
||||||
|
toxygen = toxygen.__main__:iMain
|
||||||
|
|
||||||
|
[easy_install]
|
||||||
|
zip_ok = false
|
||||||
|
|
||||||
|
[flake8]
|
||||||
|
jobs = 1
|
||||||
|
max-line-length = 88
|
||||||
|
ignore =
|
||||||
|
E111
|
||||||
|
E114
|
||||||
|
E128
|
||||||
|
E225
|
||||||
|
E261
|
||||||
|
E302
|
||||||
|
E305
|
||||||
|
E402
|
||||||
|
E501
|
||||||
|
E502
|
||||||
|
E541
|
||||||
|
E701
|
||||||
|
E702
|
||||||
|
E704
|
||||||
|
E722
|
||||||
|
E741
|
||||||
|
F508
|
||||||
|
F541
|
||||||
|
W503
|
||||||
|
W601
|
93
setup.py
93
setup.py
@ -1,93 +0,0 @@
|
|||||||
from setuptools import setup
|
|
||||||
from setuptools.command.install import install
|
|
||||||
from platform import system
|
|
||||||
from subprocess import call
|
|
||||||
import main
|
|
||||||
import sys
|
|
||||||
import os
|
|
||||||
from utils.util import curr_directory, join_path
|
|
||||||
|
|
||||||
|
|
||||||
version = main.__version__ + '.0'
|
|
||||||
|
|
||||||
|
|
||||||
if system() == 'Windows':
|
|
||||||
MODULES = ['PyQt5', 'PyAudio', 'numpy', 'opencv-python', 'pydenticon', 'cv2']
|
|
||||||
else:
|
|
||||||
MODULES = ['pydenticon']
|
|
||||||
MODULES.append('PyQt5')
|
|
||||||
try:
|
|
||||||
import pyaudio
|
|
||||||
except ImportError:
|
|
||||||
MODULES.append('PyAudio')
|
|
||||||
try:
|
|
||||||
import numpy
|
|
||||||
except ImportError:
|
|
||||||
MODULES.append('numpy')
|
|
||||||
try:
|
|
||||||
import cv2
|
|
||||||
except ImportError:
|
|
||||||
MODULES.append('opencv-python')
|
|
||||||
try:
|
|
||||||
import coloredlogs
|
|
||||||
except ImportError:
|
|
||||||
MODULES.append('coloredlogs')
|
|
||||||
try:
|
|
||||||
import pyqtconsole
|
|
||||||
except ImportError:
|
|
||||||
MODULES.append('pyqtconsole')
|
|
||||||
|
|
||||||
|
|
||||||
def get_packages():
|
|
||||||
directory = join_path(curr_directory(__file__), 'toxygen')
|
|
||||||
for root, dirs, files in os.walk(directory):
|
|
||||||
packages = map(lambda d: 'toxygen.' + d, dirs)
|
|
||||||
packages = ['toxygen'] + list(packages)
|
|
||||||
return packages
|
|
||||||
|
|
||||||
class InstallScript(install):
|
|
||||||
"""This class configures Toxygen after installation"""
|
|
||||||
|
|
||||||
def run(self):
|
|
||||||
install.run(self)
|
|
||||||
try:
|
|
||||||
if system() != 'Windows':
|
|
||||||
call(["toxygen", "--clean"])
|
|
||||||
except:
|
|
||||||
try:
|
|
||||||
params = list(filter(lambda x: x.startswith('--prefix='), sys.argv))
|
|
||||||
if params:
|
|
||||||
path = params[0][len('--prefix='):]
|
|
||||||
if path[-1] not in ('/', '\\'):
|
|
||||||
path += '/'
|
|
||||||
path += 'bin/toxygen'
|
|
||||||
if system() != 'Windows':
|
|
||||||
call([path, "--clean"])
|
|
||||||
except:
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
setup(name='Toxygen',
|
|
||||||
version=version,
|
|
||||||
description='Toxygen - Tox client',
|
|
||||||
long_description='Toxygen is powerful Tox client written in Python3',
|
|
||||||
url='https://git.plastiras.org/emdee/toxygen/',
|
|
||||||
keywords='toxygen Tox messenger',
|
|
||||||
author='Ingvar',
|
|
||||||
maintainer='',
|
|
||||||
license='GPL3',
|
|
||||||
packages=get_packages(),
|
|
||||||
install_requires=MODULES,
|
|
||||||
include_package_data=True,
|
|
||||||
classifiers=[
|
|
||||||
'Programming Language :: Python :: 3 :: Only',
|
|
||||||
'Programming Language :: Python :: 3.9',
|
|
||||||
],
|
|
||||||
entry_points={
|
|
||||||
'console_scripts': ['toxygen=toxygen.main:main']
|
|
||||||
},
|
|
||||||
cmdclass={
|
|
||||||
'install': InstallScript
|
|
||||||
},
|
|
||||||
zip_safe=False
|
|
||||||
)
|
|
53
setup.py.dst
Normal file
53
setup.py.dst
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
import sys
|
||||||
|
import os
|
||||||
|
from setuptools import setup
|
||||||
|
from setuptools.command.install import install
|
||||||
|
|
||||||
|
version = '1.0.0'
|
||||||
|
|
||||||
|
MODULES = open('requirements.txt', 'rt').readlines()
|
||||||
|
|
||||||
|
def get_packages():
|
||||||
|
directory = os.path.join(os.path.dirname(__file__), 'tox_wrapper')
|
||||||
|
for root, dirs, files in os.walk(directory):
|
||||||
|
packages = map(lambda d: 'toxygen.' + d, dirs)
|
||||||
|
packages = ['toxygen'] + list(packages)
|
||||||
|
return packages
|
||||||
|
|
||||||
|
class InstallScript(install):
|
||||||
|
"""This class configures Toxygen after installation"""
|
||||||
|
|
||||||
|
def run(self):
|
||||||
|
install.run(self)
|
||||||
|
|
||||||
|
setup(name='Toxygen',
|
||||||
|
version=version,
|
||||||
|
description='Toxygen - Tox client',
|
||||||
|
long_description='Toxygen is powerful Tox client written in Python3',
|
||||||
|
url='https://git.plastiras.org/emdee/toxygen/',
|
||||||
|
keywords='toxygen Tox messenger',
|
||||||
|
author='Ingvar',
|
||||||
|
maintainer='',
|
||||||
|
license='GPL3',
|
||||||
|
packages=get_packages(),
|
||||||
|
install_requires=MODULES,
|
||||||
|
include_package_data=True,
|
||||||
|
classifiers=[
|
||||||
|
'Programming Language :: Python :: 3 :: Only',
|
||||||
|
"Programming Language :: Python :: 3.6",
|
||||||
|
"Programming Language :: Python :: 3.7",
|
||||||
|
"Programming Language :: Python :: 3.8",
|
||||||
|
"Programming Language :: Python :: 3.9",
|
||||||
|
"Programming Language :: Python :: 3.10",
|
||||||
|
"Programming Language :: Python :: 3.11",
|
||||||
|
'Programming Language :: Python :: 3.11',
|
||||||
|
],
|
||||||
|
entry_points={
|
||||||
|
'console_scripts': ['toxygen=toxygen.main:main']
|
||||||
|
},
|
||||||
|
package_data={"": ["*.ui"],},
|
||||||
|
cmdclass={
|
||||||
|
'install': InstallScript,
|
||||||
|
},
|
||||||
|
zip_safe=False
|
||||||
|
)
|
@ -1,4 +1,4 @@
|
|||||||
class TestToxygen:
|
class TestToxygen:
|
||||||
|
|
||||||
def test_main(self):
|
def test_main(self):
|
||||||
import toxygen.main # check for syntax errors
|
import toxygen.__main__ # check for syntax errors
|
||||||
|
14
toxygen/.pylint.sh
Executable file
14
toxygen/.pylint.sh
Executable file
@ -0,0 +1,14 @@
|
|||||||
|
#!/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
|
||||||
|
|
||||||
|
sed -e "/Module 'os' has no/d" \
|
||||||
|
-e "/Undefined variable 'app'/d" \
|
||||||
|
-e '/tests\//d' \
|
||||||
|
-e "/Instance of 'Curl' has no /d" \
|
||||||
|
-e "/No name 'path' in module 'os' /d" \
|
||||||
|
-e "/ in module 'os'/d" \
|
||||||
|
-e "/.bak\//d" \
|
||||||
|
-i .pylint.err .pylint.out
|
@ -1,8 +1,3 @@
|
|||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
path = os.path.dirname(os.path.realpath(__file__)) # curr dir
|
|
||||||
|
|
||||||
sys.path.insert(0, os.path.join(path, 'styles'))
|
|
||||||
sys.path.insert(0, os.path.join(path, 'plugins'))
|
|
||||||
sys.path.insert(0, path)
|
|
||||||
|
@ -1,22 +1,22 @@
|
|||||||
# -*- mode: python; indent-tabs-mode: nil; py-indent-offset: 4; coding: utf-8 -*-
|
# -*- mode: python; indent-tabs-mode: nil; py-indent-offset: 4; coding: utf-8 -*-
|
||||||
import sys
|
import sys
|
||||||
import os
|
import os
|
||||||
import app
|
|
||||||
import argparse
|
|
||||||
import logging
|
import logging
|
||||||
import signal
|
import signal
|
||||||
|
import time
|
||||||
import faulthandler
|
|
||||||
faulthandler.enable()
|
|
||||||
|
|
||||||
import warnings
|
import warnings
|
||||||
|
import faulthandler
|
||||||
|
|
||||||
|
from gevent import monkey; monkey.patch_all(); del monkey # noqa
|
||||||
|
|
||||||
|
faulthandler.enable()
|
||||||
warnings.filterwarnings('ignore')
|
warnings.filterwarnings('ignore')
|
||||||
|
|
||||||
import wrapper_tests.support_testing as ts
|
import toxygen_wrapper.tests.support_testing as ts
|
||||||
try:
|
try:
|
||||||
from trepan.interfaces import server as Mserver
|
from trepan.interfaces import server as Mserver
|
||||||
from trepan.api import debug
|
from trepan.api import debug
|
||||||
except:
|
except Exception as e:
|
||||||
print('trepan3 TCP server NOT enabled.')
|
print('trepan3 TCP server NOT enabled.')
|
||||||
else:
|
else:
|
||||||
import signal
|
import signal
|
||||||
@ -25,6 +25,7 @@ else:
|
|||||||
print('trepan3 TCP server enabled on port 6666.')
|
print('trepan3 TCP server enabled on port 6666.')
|
||||||
except: pass
|
except: pass
|
||||||
|
|
||||||
|
import app
|
||||||
from user_data.settings import *
|
from user_data.settings import *
|
||||||
from user_data.settings import Settings
|
from user_data.settings import Settings
|
||||||
from user_data import settings
|
from user_data import settings
|
||||||
@ -33,21 +34,28 @@ with ts.ignoreStderr():
|
|||||||
import pyaudio
|
import pyaudio
|
||||||
|
|
||||||
__maintainer__ = 'Ingvar'
|
__maintainer__ = 'Ingvar'
|
||||||
__version__ = '0.5.0+'
|
__version__ = '1.0.0' # was 0.5.0+
|
||||||
|
|
||||||
|
path = os.path.dirname(os.path.realpath(__file__)) # curr dir
|
||||||
|
sys.path.insert(0, os.path.join(path, 'styles'))
|
||||||
|
sys.path.insert(0, os.path.join(path, 'plugins'))
|
||||||
|
# sys.path.insert(0, os.path.join(path, 'third_party'))
|
||||||
|
sys.path.insert(0, path)
|
||||||
|
|
||||||
import time
|
|
||||||
sleep = time.sleep
|
sleep = time.sleep
|
||||||
|
|
||||||
def reset():
|
os.environ['QT_API'] = os.environ.get('QT_API', 'pyqt5')
|
||||||
|
|
||||||
|
def reset() -> None:
|
||||||
Settings.reset_auto_profile()
|
Settings.reset_auto_profile()
|
||||||
|
|
||||||
def clean():
|
def clean() -> None:
|
||||||
"""Removes libs folder"""
|
"""Removes libs folder"""
|
||||||
directory = util.get_libs_directory()
|
directory = util.get_libs_directory()
|
||||||
util.remove(directory)
|
util.remove(directory)
|
||||||
|
|
||||||
def print_toxygen_version():
|
def print_toxygen_version() -> None:
|
||||||
print('Toxygen ' + __version__)
|
print('toxygen ' + __version__)
|
||||||
|
|
||||||
def setup_default_audio():
|
def setup_default_audio():
|
||||||
# need:
|
# need:
|
||||||
@ -73,8 +81,15 @@ def setup_default_audio():
|
|||||||
|
|
||||||
def setup_video(oArgs):
|
def setup_video(oArgs):
|
||||||
video = setup_default_video()
|
video = setup_default_video()
|
||||||
if oArgs.video_input == '-1':
|
# this is messed up - no video_input in oArgs
|
||||||
video['device'] = video['output_devices'][1]
|
# parser.add_argument('--video_input', type=str,)
|
||||||
|
print(video)
|
||||||
|
if not video or not video['output_devices']:
|
||||||
|
video['device'] = -1
|
||||||
|
if not hasattr(oArgs, 'video_input'):
|
||||||
|
video['device'] = video['output_devices'][0]
|
||||||
|
elif oArgs.video_input == '-1':
|
||||||
|
video['device'] = video['output_devices'][-1]
|
||||||
else:
|
else:
|
||||||
video['device'] = oArgs.video_input
|
video['device'] = oArgs.video_input
|
||||||
return video
|
return video
|
||||||
@ -166,13 +181,12 @@ def setup_audio(oArgs):
|
|||||||
def setup_default_video():
|
def setup_default_video():
|
||||||
default_video = ["-1"]
|
default_video = ["-1"]
|
||||||
default_video.extend(ts.get_video_indexes())
|
default_video.extend(ts.get_video_indexes())
|
||||||
LOG.info(f"Video input choices: {default_video!r}")
|
LOG.info(f"Video input choices: {default_video}")
|
||||||
video = {'device': -1, 'width': 320, 'height': 240, 'x': 0, 'y': 0}
|
video = {'device': -1, 'width': 320, 'height': 240, 'x': 0, 'y': 0}
|
||||||
video['output_devices'] = default_video
|
video['output_devices'] = default_video
|
||||||
return video
|
return video
|
||||||
|
|
||||||
def main_parser():
|
def main_parser(_=None, iMode=2):
|
||||||
import cv2
|
|
||||||
if not os.path.exists('/proc/sys/net/ipv6'):
|
if not os.path.exists('/proc/sys/net/ipv6'):
|
||||||
bIpV6 = 'False'
|
bIpV6 = 'False'
|
||||||
else:
|
else:
|
||||||
@ -180,35 +194,19 @@ def main_parser():
|
|||||||
lIpV6Choices=[bIpV6, 'False']
|
lIpV6Choices=[bIpV6, 'False']
|
||||||
|
|
||||||
audio = setup_default_audio()
|
audio = setup_default_audio()
|
||||||
default_video = setup_default_video()
|
default_video = setup_default_video()['output_devices']
|
||||||
|
|
||||||
logfile = os.path.join(os.environ.get('TMPDIR', '/tmp'), 'toxygen.log')
|
parser = ts.oMainArgparser()
|
||||||
parser = argparse.ArgumentParser()
|
|
||||||
parser.add_argument('--version', action='store_true', help='Prints Toxygen version')
|
parser.add_argument('--version', action='store_true', help='Prints Toxygen version')
|
||||||
parser.add_argument('--clean', action='store_true', help='Delete toxcore libs from libs folder')
|
parser.add_argument('--clean', action='store_true', help='Delete toxcore libs from libs folder')
|
||||||
parser.add_argument('--reset', action='store_true', help='Reset default profile')
|
parser.add_argument('--reset', action='store_true', help='Reset default profile')
|
||||||
parser.add_argument('--uri', type=str, default='',
|
parser.add_argument('--uri', type=str, default='',
|
||||||
help='Add specified Tox ID to friends')
|
help='Add specified Tox ID to friends')
|
||||||
parser.add_argument('--logfile', default=logfile,
|
|
||||||
help='Filename for logging')
|
|
||||||
parser.add_argument('--loglevel', type=int, default=logging.INFO,
|
|
||||||
help='Threshold for logging (lower is more) default: 20')
|
|
||||||
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=https, 2=socks')
|
|
||||||
parser.add_argument('--tcp_port', '--tcp-port', default=0, type=int,
|
|
||||||
help='tcp port')
|
|
||||||
parser.add_argument('--auto_accept_path', '--auto-accept-path', type=str,
|
parser.add_argument('--auto_accept_path', '--auto-accept-path', type=str,
|
||||||
default=os.path.join(os.environ['HOME'], 'Downloads'),
|
default=os.path.join(os.environ['HOME'], 'Downloads'),
|
||||||
help="auto_accept_path")
|
help="auto_accept_path")
|
||||||
parser.add_argument('--mode', type=int, default=2,
|
# parser.add_argument('--mode', type=int, default=iMode,
|
||||||
help='Mode: 0=chat 1=chat+audio 2=chat+audio+video default: 0')
|
# help='Mode: 0=chat 1=chat+audio 2=chat+audio+video default: 0')
|
||||||
parser.add_argument('--font', type=str, default="Courier",
|
parser.add_argument('--font', type=str, default="Courier",
|
||||||
help='Message font')
|
help='Message font')
|
||||||
parser.add_argument('--message_font_size', type=int, default=15,
|
parser.add_argument('--message_font_size', type=int, default=15,
|
||||||
@ -216,15 +214,6 @@ def main_parser():
|
|||||||
parser.add_argument('--local_discovery_enabled',type=str,
|
parser.add_argument('--local_discovery_enabled',type=str,
|
||||||
default='False', choices=['True','False'],
|
default='False', choices=['True','False'],
|
||||||
help='Look on the local lan')
|
help='Look on the local lan')
|
||||||
parser.add_argument('--udp_enabled',type=str,
|
|
||||||
default='True', choices=['True','False'],
|
|
||||||
help='En/Disable udp')
|
|
||||||
parser.add_argument('--trace_enabled',type=str,
|
|
||||||
default='False', choices=['True','False'],
|
|
||||||
help='Debugging from toxcore logger_trace')
|
|
||||||
parser.add_argument('--ipv6_enabled',type=str,
|
|
||||||
default=bIpV6, choices=lIpV6Choices,
|
|
||||||
help='En/Disable ipv6')
|
|
||||||
parser.add_argument('--compact_mode',type=str,
|
parser.add_argument('--compact_mode',type=str,
|
||||||
default='True', choices=['True','False'],
|
default='True', choices=['True','False'],
|
||||||
help='Compact mode')
|
help='Compact mode')
|
||||||
@ -243,31 +232,15 @@ def main_parser():
|
|||||||
parser.add_argument('--core_logging',type=str,
|
parser.add_argument('--core_logging',type=str,
|
||||||
default='False', choices=['True','False'],
|
default='False', choices=['True','False'],
|
||||||
help='Dis/Enable Toxcore notifications')
|
help='Dis/Enable Toxcore notifications')
|
||||||
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')
|
|
||||||
parser.add_argument('--save_history',type=str,
|
parser.add_argument('--save_history',type=str,
|
||||||
default='True', choices=['True','False'],
|
default='True', choices=['True','False'],
|
||||||
help='En/Disable save history')
|
help='En/Disable save history')
|
||||||
parser.add_argument('--update', type=int, default=0,
|
parser.add_argument('--update', type=int, default=0,
|
||||||
choices=[0,0],
|
choices=[0,0],
|
||||||
help='Update program (broken)')
|
help='Update program (broken)')
|
||||||
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='')
|
|
||||||
parser.add_argument('--download_nodes_url', type=str,
|
|
||||||
default='https://nodes.tox.chat/json')
|
|
||||||
parser.add_argument('--network', type=str,
|
|
||||||
choices=['old', 'main', 'new', 'local', 'newlocal'],
|
|
||||||
default='old')
|
|
||||||
parser.add_argument('--video_input', type=str,
|
parser.add_argument('--video_input', type=str,
|
||||||
default=-1,
|
default=-1,
|
||||||
choices=default_video['output_devices'],
|
choices=default_video,
|
||||||
help="Video input device number - /dev/video?")
|
help="Video input device number - /dev/video?")
|
||||||
parser.add_argument('--audio_input', type=str,
|
parser.add_argument('--audio_input', type=str,
|
||||||
default=oPYA.get_default_input_device_info()['name'],
|
default=oPYA.get_default_input_device_info()['name'],
|
||||||
@ -280,10 +253,10 @@ def main_parser():
|
|||||||
parser.add_argument('--theme', type=str, default='default',
|
parser.add_argument('--theme', type=str, default='default',
|
||||||
choices=['dark', 'default'],
|
choices=['dark', 'default'],
|
||||||
help='Theme - style of UI')
|
help='Theme - style of UI')
|
||||||
parser.add_argument('--sleep', type=str, default='time',
|
# parser.add_argument('--sleep', type=str, default='time',
|
||||||
# could expand this to tk, gtk, gevent...
|
# # could expand this to tk, gtk, gevent...
|
||||||
choices=['qt','gevent','time'],
|
# choices=['qt','gevent','time'],
|
||||||
help='Sleep method - one of qt, gevent , time')
|
# help='Sleep method - one of qt, gevent , time')
|
||||||
supported_languages = settings.supported_languages()
|
supported_languages = settings.supported_languages()
|
||||||
parser.add_argument('--language', type=str, default='English',
|
parser.add_argument('--language', type=str, default='English',
|
||||||
choices=supported_languages,
|
choices=supported_languages,
|
||||||
@ -308,6 +281,8 @@ lKEEP_SETTINGS = ['uri',
|
|||||||
'ipv6_enabled',
|
'ipv6_enabled',
|
||||||
'udp_enabled',
|
'udp_enabled',
|
||||||
'local_discovery_enabled',
|
'local_discovery_enabled',
|
||||||
|
'trace_enabled',
|
||||||
|
|
||||||
'theme',
|
'theme',
|
||||||
'network',
|
'network',
|
||||||
'message_font_size',
|
'message_font_size',
|
||||||
@ -325,9 +300,11 @@ lKEEP_SETTINGS = ['uri',
|
|||||||
|
|
||||||
class A(): pass
|
class A(): pass
|
||||||
|
|
||||||
def main(lArgs):
|
def main(lArgs=None) -> int:
|
||||||
global oPYA
|
global oPYA
|
||||||
from argparse import Namespace
|
from argparse import Namespace
|
||||||
|
if lArgs is None:
|
||||||
|
lArgs = sys.argv[1:]
|
||||||
parser = main_parser()
|
parser = main_parser()
|
||||||
default_ns = parser.parse_args([])
|
default_ns = parser.parse_args([])
|
||||||
oArgs = parser.parse_args(lArgs)
|
oArgs = parser.parse_args(lArgs)
|
||||||
@ -358,6 +335,7 @@ def main(lArgs):
|
|||||||
aArgs = A()
|
aArgs = A()
|
||||||
for key in oArgs.__dict__.keys():
|
for key in oArgs.__dict__.keys():
|
||||||
setattr(aArgs, key, getattr(oArgs, key))
|
setattr(aArgs, key, getattr(oArgs, key))
|
||||||
|
|
||||||
#setattr(aArgs, 'video', setup_video(oArgs))
|
#setattr(aArgs, 'video', setup_video(oArgs))
|
||||||
aArgs.video = setup_video(oArgs)
|
aArgs.video = setup_video(oArgs)
|
||||||
assert 'video' in aArgs.__dict__
|
assert 'video' in aArgs.__dict__
|
||||||
@ -367,10 +345,13 @@ def main(lArgs):
|
|||||||
assert 'audio' in aArgs.__dict__
|
assert 'audio' in aArgs.__dict__
|
||||||
oArgs = aArgs
|
oArgs = aArgs
|
||||||
|
|
||||||
toxygen = app.App(__version__, oArgs)
|
oApp = app.App(__version__, oArgs)
|
||||||
# for pyqtconsole
|
# for pyqtconsole
|
||||||
__builtins__.app = toxygen
|
try:
|
||||||
i = toxygen.iMain()
|
setattr(__builtins__, 'app', oApp)
|
||||||
|
except Exception as e:
|
||||||
|
pass
|
||||||
|
i = oApp.iMain()
|
||||||
return i
|
return i
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
290
toxygen/app.py
290
toxygen/app.py
@ -2,17 +2,21 @@
|
|||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
import traceback
|
import traceback
|
||||||
|
import logging
|
||||||
from random import shuffle
|
from random import shuffle
|
||||||
import threading
|
import threading
|
||||||
from time import sleep
|
from time import sleep, time
|
||||||
|
from copy import deepcopy
|
||||||
|
|
||||||
from gevent import monkey; monkey.patch_all(); del monkey # noqa
|
# used only in loop
|
||||||
import gevent
|
import gevent
|
||||||
|
|
||||||
from PyQt5 import QtWidgets, QtGui, QtCore
|
from qtpy import QtWidgets, QtGui, QtCore
|
||||||
from qtpy.QtCore import QTimer
|
from qtpy.QtCore import QTimer
|
||||||
from qtpy.QtWidgets import QApplication
|
from qtpy.QtWidgets import QApplication
|
||||||
|
|
||||||
|
__version__ = "1.0.0"
|
||||||
|
|
||||||
try:
|
try:
|
||||||
import coloredlogs
|
import coloredlogs
|
||||||
if 'COLOREDLOGS_LEVEL_STYLES' not in os.environ:
|
if 'COLOREDLOGS_LEVEL_STYLES' not in os.environ:
|
||||||
@ -21,14 +25,22 @@ try:
|
|||||||
except ImportError as e:
|
except ImportError as e:
|
||||||
coloredlogs = False
|
coloredlogs = False
|
||||||
|
|
||||||
# install https://github.com/weechat/qweechat
|
try:
|
||||||
# if you want IRC and jabber
|
# https://github.com/pyqtconsole/pyqtconsole
|
||||||
|
from pyqtconsole.console import PythonConsole
|
||||||
|
except Exception as e:
|
||||||
|
PythonConsole = None
|
||||||
|
|
||||||
|
try:
|
||||||
|
import qdarkstylexxx
|
||||||
|
except ImportError:
|
||||||
|
qdarkstyle = None
|
||||||
|
|
||||||
from middleware import threads
|
from middleware import threads
|
||||||
import middleware.callbacks as callbacks
|
import middleware.callbacks as callbacks
|
||||||
import updater.updater as updater
|
import updater.updater as updater
|
||||||
from middleware.tox_factory import tox_factory
|
from middleware.tox_factory import tox_factory
|
||||||
import wrapper.toxencryptsave as tox_encrypt_save
|
import toxygen_wrapper.toxencryptsave as tox_encrypt_save
|
||||||
import user_data.toxes
|
import user_data.toxes
|
||||||
from user_data import settings
|
from user_data import settings
|
||||||
from user_data.settings import get_user_config_path, merge_args_into_settings
|
from user_data.settings import get_user_config_path, merge_args_into_settings
|
||||||
@ -66,16 +78,16 @@ from ui.widgets_factory import WidgetsFactory
|
|||||||
from user_data.backup_service import BackupService
|
from user_data.backup_service import BackupService
|
||||||
import styles.style # TODO: dynamic loading
|
import styles.style # TODO: dynamic loading
|
||||||
|
|
||||||
import wrapper_tests.support_testing as ts
|
import toxygen_wrapper.tests.support_testing as ts
|
||||||
|
|
||||||
global LOG
|
global LOG
|
||||||
import logging
|
|
||||||
LOG = logging.getLogger('app')
|
LOG = logging.getLogger('app')
|
||||||
|
|
||||||
IDLE_PERIOD = 0.10
|
IDLE_PERIOD = 0.10
|
||||||
iNODES=8
|
iNODES=8
|
||||||
|
bSHOW_TRAY=False
|
||||||
|
|
||||||
def setup_logging(oArgs):
|
def setup_logging(oArgs) -> None:
|
||||||
global LOG
|
global LOG
|
||||||
logging._defaultFormatter = logging.Formatter(datefmt='%m-%d %H:%M:%S',
|
logging._defaultFormatter = logging.Formatter(datefmt='%m-%d %H:%M:%S',
|
||||||
fmt='%(levelname)s:%(name)s %(message)s')
|
fmt='%(levelname)s:%(name)s %(message)s')
|
||||||
@ -103,7 +115,7 @@ def setup_logging(oArgs):
|
|||||||
|
|
||||||
LOG.setLevel(oArgs.loglevel)
|
LOG.setLevel(oArgs.loglevel)
|
||||||
LOG.trace = lambda l: LOG.log(0, repr(l))
|
LOG.trace = lambda l: LOG.log(0, repr(l))
|
||||||
LOG.info(f"Setting loglevel to {oArgs.loglevel!s}")
|
LOG.info(f"Setting loglevel to {oArgs.loglevel}")
|
||||||
|
|
||||||
if oArgs.loglevel < 20:
|
if oArgs.loglevel < 20:
|
||||||
# opencv debug
|
# opencv debug
|
||||||
@ -139,13 +151,12 @@ sSTYLE = """
|
|||||||
.QTextSingleLine {font-family Courier; weight: 75; }
|
.QTextSingleLine {font-family Courier; weight: 75; }
|
||||||
.QToolBar { font-weight: bold; }
|
.QToolBar { font-weight: bold; }
|
||||||
"""
|
"""
|
||||||
from copy import deepcopy
|
|
||||||
class App:
|
class App:
|
||||||
|
|
||||||
def __init__(self, version, oArgs):
|
def __init__(self, version, oArgs):
|
||||||
global LOG
|
global LOG
|
||||||
self._args = oArgs
|
self._args = oArgs
|
||||||
self._oArgs = oArgs
|
self.oArgs = oArgs
|
||||||
self._path = path_to_profile = oArgs.profile
|
self._path = path_to_profile = oArgs.profile
|
||||||
uri = oArgs.uri
|
uri = oArgs.uri
|
||||||
logfile = oArgs.logfile
|
logfile = oArgs.logfile
|
||||||
@ -154,7 +165,7 @@ class App:
|
|||||||
setup_logging(oArgs)
|
setup_logging(oArgs)
|
||||||
# sys.stderr.write( 'Command line args: ' +repr(oArgs) +'\n')
|
# sys.stderr.write( 'Command line args: ' +repr(oArgs) +'\n')
|
||||||
LOG.info("Command line: " +' '.join(sys.argv[1:]))
|
LOG.info("Command line: " +' '.join(sys.argv[1:]))
|
||||||
LOG.debug(f'oArgs = {oArgs!r}')
|
LOG.debug(f'oArgs = {oArgs}')
|
||||||
LOG.info("Starting toxygen version " +version)
|
LOG.info("Starting toxygen version " +version)
|
||||||
|
|
||||||
self._version = version
|
self._version = version
|
||||||
@ -162,7 +173,8 @@ class App:
|
|||||||
self._app = self._settings = self._profile_manager = None
|
self._app = self._settings = self._profile_manager = None
|
||||||
self._plugin_loader = self._messenger = None
|
self._plugin_loader = self._messenger = None
|
||||||
self._tox = self._ms = self._init = self._main_loop = self._av_loop = None
|
self._tox = self._ms = self._init = self._main_loop = self._av_loop = None
|
||||||
self._uri = self._toxes = self._tray = self._file_transfer_handler = self._contacts_provider = None
|
self._uri = self._toxes = self._tray = None
|
||||||
|
self._file_transfer_handler = self._contacts_provider = None
|
||||||
self._friend_factory = self._calls_manager = None
|
self._friend_factory = self._calls_manager = None
|
||||||
self._contacts_manager = self._smiley_loader = None
|
self._contacts_manager = self._smiley_loader = None
|
||||||
self._group_peer_factory = self._tox_dns = self._backup_service = None
|
self._group_peer_factory = self._tox_dns = self._backup_service = None
|
||||||
@ -170,19 +182,18 @@ class App:
|
|||||||
if uri is not None and uri.startswith('tox:'):
|
if uri is not None and uri.startswith('tox:'):
|
||||||
self._uri = uri[4:]
|
self._uri = uri[4:]
|
||||||
self._history = None
|
self._history = None
|
||||||
|
self.bAppExiting = False
|
||||||
|
|
||||||
# -----------------------------------------------------------------------------------------------------------------
|
|
||||||
# Public methods
|
# Public methods
|
||||||
# -----------------------------------------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
def set_trace(self):
|
def set_trace(self) -> None:
|
||||||
"""unused"""
|
"""unused"""
|
||||||
LOG.debug('pdb.set_trace ')
|
LOG.debug('pdb.set_trace ')
|
||||||
sys.stdin = sys.__stdin__
|
sys.stdin = sys.__stdin__
|
||||||
sys.stdout = sys.__stdout__
|
sys.stdout = sys.__stdout__
|
||||||
import pdb; pdb.set_trace()
|
import pdb; pdb.set_trace()
|
||||||
|
|
||||||
def ten(self, i=0):
|
def ten(self, i=0) -> None:
|
||||||
"""unused"""
|
"""unused"""
|
||||||
global iI
|
global iI
|
||||||
iI += 1
|
iI += 1
|
||||||
@ -194,14 +205,16 @@ class App:
|
|||||||
#sys.stderr.write(f"ten '+str(iI)+' {i}"+' '+repr(LOG) +'\n')
|
#sys.stderr.write(f"ten '+str(iI)+' {i}"+' '+repr(LOG) +'\n')
|
||||||
#LOG.debug('ten '+str(iI))
|
#LOG.debug('ten '+str(iI))
|
||||||
|
|
||||||
def iMain(self):
|
def iMain(self) -> int:
|
||||||
"""
|
"""
|
||||||
Main function of app. loads login screen if needed and starts main screen
|
Main function of app. loads login screen if needed and starts main screen
|
||||||
"""
|
"""
|
||||||
self._app = QtWidgets.QApplication([])
|
self._app = QApplication([])
|
||||||
self._load_icon()
|
self._load_icon()
|
||||||
|
|
||||||
if util.get_platform() == 'Linux':
|
# is this still needed?
|
||||||
|
if util.get_platform() == 'Linux' and \
|
||||||
|
hasattr(QtCore.Qt, 'AA_X11InitThreads'):
|
||||||
QtCore.QCoreApplication.setAttribute(QtCore.Qt.AA_X11InitThreads)
|
QtCore.QCoreApplication.setAttribute(QtCore.Qt.AA_X11InitThreads)
|
||||||
|
|
||||||
self._load_base_style()
|
self._load_base_style()
|
||||||
@ -212,13 +225,11 @@ class App:
|
|||||||
# this throws everything as errors
|
# this throws everything as errors
|
||||||
if not self._select_and_load_profile():
|
if not self._select_and_load_profile():
|
||||||
return 2
|
return 2
|
||||||
if hasattr(self._oArgs, 'update') and self._oArgs.update:
|
if hasattr(self._args, 'update') and self._args.update:
|
||||||
if self._try_to_update(): return 3
|
if self._try_to_update(): return 3
|
||||||
|
|
||||||
self._load_app_styles()
|
self._load_app_styles()
|
||||||
if self._oArgs.language != 'English':
|
if self._args.language != 'English':
|
||||||
# > /var/local/src/toxygen/toxygen/app.py(303)_load_app_translations()->None
|
|
||||||
# -> self._app.translator = translator
|
|
||||||
# (Pdb) Fatal Python error: Segmentation fault
|
# (Pdb) Fatal Python error: Segmentation fault
|
||||||
self._load_app_translations()
|
self._load_app_translations()
|
||||||
self._create_dependencies()
|
self._create_dependencies()
|
||||||
@ -228,8 +239,8 @@ class App:
|
|||||||
if self._uri is not None:
|
if self._uri is not None:
|
||||||
self._ms.add_contact(self._uri)
|
self._ms.add_contact(self._uri)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
LOG.error(f"Error loading profile: {e!s}")
|
LOG.error(f"Error loading profile: {e}")
|
||||||
sys.stderr.write(' iMain(): ' +f"Error loading profile: {e!s}" \
|
sys.stderr.write(' iMain(): ' +f"Error loading profile: {e}" \
|
||||||
+'\n' + traceback.format_exc()+'\n')
|
+'\n' + traceback.format_exc()+'\n')
|
||||||
util_ui.message_box(str(e),
|
util_ui.message_box(str(e),
|
||||||
util_ui.tr('Error loading profile'))
|
util_ui.tr('Error loading profile'))
|
||||||
@ -247,11 +258,9 @@ class App:
|
|||||||
|
|
||||||
return retval
|
return retval
|
||||||
|
|
||||||
# -----------------------------------------------------------------------------------------------------------------
|
|
||||||
# App executing
|
# App executing
|
||||||
# -----------------------------------------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
def _execute_app(self):
|
def _execute_app(self) -> None:
|
||||||
LOG.debug("_execute_app")
|
LOG.debug("_execute_app")
|
||||||
|
|
||||||
while True:
|
while True:
|
||||||
@ -262,11 +271,11 @@ class App:
|
|||||||
else:
|
else:
|
||||||
break
|
break
|
||||||
|
|
||||||
def quit(self, retval=0):
|
def quit(self, retval=0) -> None:
|
||||||
LOG.debug("quit")
|
LOG.debug("quit")
|
||||||
self._stop_app()
|
self._stop_app()
|
||||||
|
|
||||||
# failsafe: segfaults on exit
|
# failsafe: segfaults on exit - maybe it's Qt
|
||||||
if hasattr(self, '_tox'):
|
if hasattr(self, '_tox'):
|
||||||
if self._tox and hasattr(self._tox, 'kill'):
|
if self._tox and hasattr(self._tox, 'kill'):
|
||||||
LOG.debug(f"quit: Killing {self._tox}")
|
LOG.debug(f"quit: Killing {self._tox}")
|
||||||
@ -287,10 +296,10 @@ class App:
|
|||||||
|
|
||||||
raise SystemExit(retval)
|
raise SystemExit(retval)
|
||||||
|
|
||||||
def _stop_app(self):
|
def _stop_app(self) -> None:
|
||||||
LOG.debug("_stop_app")
|
LOG.debug("_stop_app")
|
||||||
self._save_profile()
|
self._save_profile()
|
||||||
#? self._history.save_history()
|
self._history.save_history()
|
||||||
|
|
||||||
self._plugin_loader.stop()
|
self._plugin_loader.stop()
|
||||||
try:
|
try:
|
||||||
@ -298,56 +307,60 @@ class App:
|
|||||||
except (Exception, RuntimeError):
|
except (Exception, RuntimeError):
|
||||||
# RuntimeError: cannot join current thread
|
# RuntimeError: cannot join current thread
|
||||||
pass
|
pass
|
||||||
|
# I think there are threads still running here leading to a SEGV
|
||||||
|
# File "/usr/lib/python3.11/threading.py", line 1401 in run
|
||||||
|
# File "/usr/lib/python3.11/threading.py", line 1045 in _bootstrap_inner
|
||||||
|
# File "/usr/lib/python3.11/threading.py", line 1002 in _bootstrap
|
||||||
|
|
||||||
if hasattr(self, '_tray') and self._tray:
|
if hasattr(self, '_tray') and self._tray:
|
||||||
self._tray.hide()
|
self._tray.hide()
|
||||||
self._settings.close()
|
self._settings.close()
|
||||||
|
|
||||||
|
self.bAppExiting = True
|
||||||
LOG.debug(f"stop_app: Killing {self._tox}")
|
LOG.debug(f"stop_app: Killing {self._tox}")
|
||||||
self._kill_toxav()
|
self._kill_toxav()
|
||||||
self._kill_tox()
|
self._kill_tox()
|
||||||
del self._tox
|
del self._tox
|
||||||
|
|
||||||
oArgs = self._oArgs
|
oArgs = self._args
|
||||||
if hasattr(oArgs, 'log_oFd'):
|
if hasattr(oArgs, 'log_oFd'):
|
||||||
LOG.debug(f"Closing {oArgs.log_oFd}")
|
LOG.debug(f"Closing {oArgs.log_oFd}")
|
||||||
oArgs.log_oFd.close()
|
oArgs.log_oFd.close()
|
||||||
delattr(oArgs, 'log_oFd')
|
delattr(oArgs, 'log_oFd')
|
||||||
|
|
||||||
# -----------------------------------------------------------------------------------------------------------------
|
|
||||||
# App loading
|
# App loading
|
||||||
# -----------------------------------------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
def _load_base_style(self):
|
def _load_base_style(self) -> None:
|
||||||
if self._oArgs.theme in ['', 'default']: return
|
if self._args.theme in ['', 'default']: return
|
||||||
|
|
||||||
if qdarkstyle:
|
if qdarkstyle:
|
||||||
LOG.debug("_load_base_style qdarkstyle " +self._oArgs.theme)
|
LOG.debug("_load_base_style qdarkstyle " +self._args.theme)
|
||||||
# QDarkStyleSheet
|
# QDarkStyleSheet
|
||||||
if self._oArgs.theme == 'light':
|
if self._args.theme == 'light':
|
||||||
from qdarkstyle.light.palette import LightPalette
|
from qdarkstyle.light.palette import LightPalette
|
||||||
style = qdarkstyle.load_stylesheet(palette=LightPalette)
|
style = qdarkstyle.load_stylesheet(palette=LightPalette)
|
||||||
else:
|
else:
|
||||||
from qdarkstyle.dark.palette import DarkPalette
|
from qdarkstyle.dark.palette import DarkPalette
|
||||||
style = qdarkstyle.load_stylesheet(palette=DarkPalette)
|
style = qdarkstyle.load_stylesheet(palette=DarkPalette)
|
||||||
else:
|
else:
|
||||||
LOG.debug("_load_base_style qss " +self._oArgs.theme)
|
LOG.debug("_load_base_style qss " +self._args.theme)
|
||||||
name = self._oArgs.theme + '.qss'
|
name = self._args.theme + '.qss'
|
||||||
with open(util.join_path(util.get_styles_directory(), name)) as fl:
|
with open(util.join_path(util.get_styles_directory(), name)) as fl:
|
||||||
style = fl.read()
|
style = fl.read()
|
||||||
style += '\n' +sSTYLE
|
style += '\n' +sSTYLE
|
||||||
self._app.setStyleSheet(style)
|
self._app.setStyleSheet(style)
|
||||||
|
|
||||||
def _load_app_styles(self):
|
def _load_app_styles(self) -> None:
|
||||||
LOG.debug(f"_load_app_styles {list(settings.built_in_themes().keys())!r}")
|
LOG.debug(f"_load_app_styles {list(settings.built_in_themes().keys())}")
|
||||||
# application color scheme
|
# application color scheme
|
||||||
if self._settings['theme'] in ['', 'default']: return
|
if self._settings['theme'] in ['', 'default']: return
|
||||||
for theme in settings.built_in_themes().keys():
|
for theme in settings.built_in_themes().keys():
|
||||||
if self._settings['theme'] != theme:
|
if self._settings['theme'] != theme:
|
||||||
continue
|
continue
|
||||||
if qdarkstyle:
|
if qdarkstyle:
|
||||||
LOG.debug("_load_base_style qdarkstyle " +self._oArgs.theme)
|
LOG.debug("_load_base_style qdarkstyle " +self._args.theme)
|
||||||
# QDarkStyleSheet
|
# QDarkStyleSheet
|
||||||
if self._oArgs.theme == 'light':
|
if self._args.theme == 'light':
|
||||||
from qdarkstyle.light.palette import LightPalette
|
from qdarkstyle.light.palette import LightPalette
|
||||||
style = qdarkstyle.load_stylesheet(palette=LightPalette)
|
style = qdarkstyle.load_stylesheet(palette=LightPalette)
|
||||||
else:
|
else:
|
||||||
@ -364,10 +377,10 @@ class App:
|
|||||||
LOG.debug('_load_app_styles: loading theme file ' + file_path)
|
LOG.debug('_load_app_styles: loading theme file ' + file_path)
|
||||||
style += '\n' +sSTYLE
|
style += '\n' +sSTYLE
|
||||||
self._app.setStyleSheet(style)
|
self._app.setStyleSheet(style)
|
||||||
LOG.info('_load_app_styles: loaded theme ' +self._oArgs.theme)
|
LOG.info('_load_app_styles: loaded theme ' +self._args.theme)
|
||||||
break
|
break
|
||||||
|
|
||||||
def _load_login_screen_translations(self):
|
def _load_login_screen_translations(self) -> None:
|
||||||
LOG.debug("_load_login_screen_translations")
|
LOG.debug("_load_login_screen_translations")
|
||||||
current_language, supported_languages = self._get_languages()
|
current_language, supported_languages = self._get_languages()
|
||||||
if current_language not in supported_languages:
|
if current_language not in supported_languages:
|
||||||
@ -378,13 +391,13 @@ class App:
|
|||||||
self._app.installTranslator(translator)
|
self._app.installTranslator(translator)
|
||||||
self._app.translator = translator
|
self._app.translator = translator
|
||||||
|
|
||||||
def _load_icon(self):
|
def _load_icon(self) -> None:
|
||||||
LOG.debug("_load_icon")
|
LOG.debug("_load_icon")
|
||||||
icon_file = os.path.join(util.get_images_directory(), 'icon.png')
|
icon_file = os.path.join(util.get_images_directory(), 'icon.png')
|
||||||
self._app.setWindowIcon(QtGui.QIcon(icon_file))
|
self._app.setWindowIcon(QtGui.QIcon(icon_file))
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _get_languages():
|
def _get_languages() -> tuple:
|
||||||
LOG.debug("_get_languages")
|
LOG.debug("_get_languages")
|
||||||
current_locale = QtCore.QLocale()
|
current_locale = QtCore.QLocale()
|
||||||
curr_language = current_locale.languageToString(current_locale.language())
|
curr_language = current_locale.languageToString(current_locale.language())
|
||||||
@ -392,7 +405,7 @@ class App:
|
|||||||
|
|
||||||
return curr_language, supported_languages
|
return curr_language, supported_languages
|
||||||
|
|
||||||
def _load_app_translations(self):
|
def _load_app_translations(self) -> None:
|
||||||
LOG.debug("_load_app_translations")
|
LOG.debug("_load_app_translations")
|
||||||
lang = settings.supported_languages()[self._settings['language']]
|
lang = settings.supported_languages()[self._settings['language']]
|
||||||
translator = QtCore.QTranslator()
|
translator = QtCore.QTranslator()
|
||||||
@ -400,13 +413,13 @@ class App:
|
|||||||
self._app.installTranslator(translator)
|
self._app.installTranslator(translator)
|
||||||
self._app.translator = translator
|
self._app.translator = translator
|
||||||
|
|
||||||
def _select_and_load_profile(self):
|
def _select_and_load_profile(self) -> bool:
|
||||||
LOG.debug("_select_and_load_profile: " +repr(self._path))
|
LOG.debug("_select_and_load_profile: " +repr(self._path))
|
||||||
|
|
||||||
if self._path is not None:
|
if self._path is not None:
|
||||||
# toxygen was started with path to profile
|
# toxygen was started with path to profile
|
||||||
try:
|
try:
|
||||||
assert os.path.exists(self._path), self._path
|
assert os.path.exists(self._path), f"FNF {self._path}"
|
||||||
self._load_existing_profile(self._path)
|
self._load_existing_profile(self._path)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
LOG.error('_load_existing_profile failed: ' + str(e))
|
LOG.error('_load_existing_profile failed: ' + str(e))
|
||||||
@ -458,16 +471,15 @@ class App:
|
|||||||
if not reply:
|
if not reply:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
self._settings.set_active_profile()
|
# is self._path right - was pathless
|
||||||
|
self._settings.set_active_profile(self._path)
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
# -----------------------------------------------------------------------------------------------------------------
|
|
||||||
# Threads
|
# Threads
|
||||||
# -----------------------------------------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
def _start_threads(self, initial_start=True):
|
def _start_threads(self, initial_start=True) -> None:
|
||||||
LOG.debug(f"_start_threads before: {threading.enumerate()!r}")
|
LOG.debug(f"_start_threads before: {threading.enumerate()}")
|
||||||
# init thread
|
# init thread
|
||||||
self._init = threads.InitThread(self._tox,
|
self._init = threads.InitThread(self._tox,
|
||||||
self._plugin_loader,
|
self._plugin_loader,
|
||||||
@ -476,10 +488,10 @@ class App:
|
|||||||
initial_start)
|
initial_start)
|
||||||
self._init.start()
|
self._init.start()
|
||||||
def te(): return [t.name for t in threading.enumerate()]
|
def te(): return [t.name for t in threading.enumerate()]
|
||||||
LOG.debug(f"_start_threads init: {te()!r}")
|
LOG.debug(f"_start_threads init: {te()}")
|
||||||
|
|
||||||
# starting threads for tox iterate and toxav iterate
|
# starting threads for tox iterate and toxav iterate
|
||||||
self._main_loop = threads.ToxIterateThread(self._tox)
|
self._main_loop = threads.ToxIterateThread(self._tox, app=self)
|
||||||
self._main_loop.start()
|
self._main_loop.start()
|
||||||
|
|
||||||
self._av_loop = threads.ToxAVIterateThread(self._tox.AV)
|
self._av_loop = threads.ToxAVIterateThread(self._tox.AV)
|
||||||
@ -487,9 +499,9 @@ class App:
|
|||||||
|
|
||||||
if initial_start:
|
if initial_start:
|
||||||
threads.start_file_transfer_thread()
|
threads.start_file_transfer_thread()
|
||||||
LOG.debug(f"_start_threads after: {[t.name for t in threading.enumerate()]!r}")
|
LOG.debug(f"_start_threads after: {[t.name for t in threading.enumerate()]}")
|
||||||
|
|
||||||
def _stop_threads(self, is_app_closing=True):
|
def _stop_threads(self, is_app_closing=True) -> None:
|
||||||
LOG.debug("_stop_threads")
|
LOG.debug("_stop_threads")
|
||||||
self._init.stop_thread(1.0)
|
self._init.stop_thread(1.0)
|
||||||
|
|
||||||
@ -499,19 +511,19 @@ class App:
|
|||||||
if is_app_closing:
|
if is_app_closing:
|
||||||
threads.stop_file_transfer_thread()
|
threads.stop_file_transfer_thread()
|
||||||
|
|
||||||
def iterate(self, n=100):
|
def iterate(self, n=100) -> None:
|
||||||
interval = self._tox.iteration_interval()
|
interval = self._tox.iteration_interval()
|
||||||
for i in range(n):
|
for i in range(n):
|
||||||
self._tox.iterate()
|
self._tox.iterate()
|
||||||
|
# Cooperative yield, allow gevent to monitor file handles via libevent
|
||||||
gevent.sleep(interval / 1000.0)
|
gevent.sleep(interval / 1000.0)
|
||||||
|
#? sleep(interval / 1000.0)
|
||||||
|
|
||||||
# -----------------------------------------------------------------------------------------------------------------
|
|
||||||
# Profiles
|
# Profiles
|
||||||
# -----------------------------------------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
def _select_profile(self):
|
def _select_profile(self):
|
||||||
LOG.debug("_select_profile")
|
LOG.debug("_select_profile")
|
||||||
if self._oArgs.language != 'English':
|
if self._args.language != 'English':
|
||||||
self._load_login_screen_translations()
|
self._load_login_screen_translations()
|
||||||
ls = LoginScreen()
|
ls = LoginScreen()
|
||||||
profiles = ProfileManager.find_profiles()
|
profiles = ProfileManager.find_profiles()
|
||||||
@ -520,23 +532,27 @@ class App:
|
|||||||
self._app.exec_()
|
self._app.exec_()
|
||||||
return ls.result
|
return ls.result
|
||||||
|
|
||||||
def _load_existing_profile(self, profile_path):
|
def _load_existing_profile(self, profile_path) -> None:
|
||||||
|
profile_path = profile_path.replace('.json', '.tox')
|
||||||
LOG.info("_load_existing_profile " +repr(profile_path))
|
LOG.info("_load_existing_profile " +repr(profile_path))
|
||||||
assert os.path.exists(profile_path), profile_path
|
assert os.path.exists(profile_path), profile_path
|
||||||
self._profile_manager = ProfileManager(self._toxes, profile_path)
|
self._profile_manager = ProfileManager(self._toxes, profile_path, app=self)
|
||||||
data = self._profile_manager.open_profile()
|
data = self._profile_manager.open_profile()
|
||||||
if self._toxes.is_data_encrypted(data):
|
if self._toxes.is_data_encrypted(data):
|
||||||
LOG.debug("_entering password")
|
LOG.debug("_entering password")
|
||||||
data = self._enter_password(data)
|
data = self._enter_password(data)
|
||||||
LOG.debug("_entered password")
|
LOG.debug("_entered password")
|
||||||
json_file = profile_path.replace('.tox', '.json')
|
json_file = profile_path.replace('.tox', '.json')
|
||||||
assert os.path.exists(json_file), json_file
|
if os.path.exists(json_file):
|
||||||
LOG.debug("creating _settings from: " +json_file)
|
LOG.debug("creating _settings from: " +json_file)
|
||||||
self._settings = Settings(self._toxes, json_file, self)
|
self._settings = Settings(self._toxes, json_file, self)
|
||||||
|
else:
|
||||||
|
self._settings = Settings.get_default_settings()
|
||||||
|
|
||||||
self._tox = self._create_tox(data, self._settings)
|
self._tox = self._create_tox(data, self._settings)
|
||||||
LOG.debug("created _tox")
|
LOG.debug("created _tox")
|
||||||
|
|
||||||
def _create_new_profile(self, profile_name):
|
def _create_new_profile(self, profile_name) -> bool:
|
||||||
LOG.info("_create_new_profile " + profile_name)
|
LOG.info("_create_new_profile " + profile_name)
|
||||||
result = self._get_create_profile_screen_result()
|
result = self._get_create_profile_screen_result()
|
||||||
if result is None:
|
if result is None:
|
||||||
@ -550,7 +566,7 @@ class App:
|
|||||||
util_ui.tr('Error'))
|
util_ui.tr('Error'))
|
||||||
return False
|
return False
|
||||||
name = profile_name or 'toxygen_user'
|
name = profile_name or 'toxygen_user'
|
||||||
assert self._oArgs
|
assert self._args
|
||||||
self._path = profile_path
|
self._path = profile_path
|
||||||
if result.password:
|
if result.password:
|
||||||
self._toxes.set_password(result.password)
|
self._toxes.set_password(result.password)
|
||||||
@ -587,14 +603,12 @@ class App:
|
|||||||
|
|
||||||
return cps.result
|
return cps.result
|
||||||
|
|
||||||
def _save_profile(self, data=None):
|
def _save_profile(self, data=None) -> None:
|
||||||
LOG.debug("_save_profile")
|
LOG.debug("_save_profile")
|
||||||
data = data or self._tox.get_savedata()
|
data = data or self._tox.get_savedata()
|
||||||
self._profile_manager.save_profile(data)
|
self._profile_manager.save_profile(data)
|
||||||
|
|
||||||
# -----------------------------------------------------------------------------------------------------------------
|
|
||||||
# Other private methods
|
# Other private methods
|
||||||
# -----------------------------------------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
def _enter_password(self, data):
|
def _enter_password(self, data):
|
||||||
"""
|
"""
|
||||||
@ -610,7 +624,7 @@ class App:
|
|||||||
self._force_exit(0)
|
self._force_exit(0)
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def _reset(self):
|
def _reset(self) -> None:
|
||||||
LOG.debug("_reset")
|
LOG.debug("_reset")
|
||||||
"""
|
"""
|
||||||
Create new tox instance (new network settings)
|
Create new tox instance (new network settings)
|
||||||
@ -650,9 +664,9 @@ class App:
|
|||||||
text = util_ui.tr('Error:') + str(e)
|
text = util_ui.tr('Error:') + str(e)
|
||||||
util_ui.message_box(text, title)
|
util_ui.message_box(text, title)
|
||||||
|
|
||||||
def _create_dependencies(self):
|
def _create_dependencies(self) -> None:
|
||||||
LOG.info(f"_create_dependencies toxygen version {self._version}")
|
LOG.info(f"_create_dependencies toxygen version {self._version}")
|
||||||
if hasattr(self._oArgs, 'update') and self._oArgs.update:
|
if hasattr(self._args, 'update') and self._args.update:
|
||||||
self._backup_service = BackupService(self._settings,
|
self._backup_service = BackupService(self._settings,
|
||||||
self._profile_manager)
|
self._profile_manager)
|
||||||
self._smiley_loader = SmileyLoader(self._settings)
|
self._smiley_loader = SmileyLoader(self._settings)
|
||||||
@ -684,7 +698,8 @@ class App:
|
|||||||
self._contacts_provider = ContactProvider(self._tox,
|
self._contacts_provider = ContactProvider(self._tox,
|
||||||
self._friend_factory,
|
self._friend_factory,
|
||||||
self._group_factory,
|
self._group_factory,
|
||||||
self._group_peer_factory)
|
self._group_peer_factory,
|
||||||
|
app=self)
|
||||||
self._profile = Profile(self._profile_manager,
|
self._profile = Profile(self._profile_manager,
|
||||||
self._tox,
|
self._tox,
|
||||||
self._ms,
|
self._ms,
|
||||||
@ -743,7 +758,7 @@ class App:
|
|||||||
self._groups_service,
|
self._groups_service,
|
||||||
history,
|
history,
|
||||||
self._contacts_provider)
|
self._contacts_provider)
|
||||||
if False:
|
if bSHOW_TRAY:
|
||||||
self._tray = tray.init_tray(self._profile,
|
self._tray = tray.init_tray(self._profile,
|
||||||
self._settings,
|
self._settings,
|
||||||
self._ms, self._toxes)
|
self._ms, self._toxes)
|
||||||
@ -758,19 +773,19 @@ class App:
|
|||||||
self._calls_manager,
|
self._calls_manager,
|
||||||
self._groups_service, self._toxes, self)
|
self._groups_service, self._toxes, self)
|
||||||
|
|
||||||
if False:
|
if bSHOW_TRAY: # broken
|
||||||
# the tray icon does not die with the app
|
# the tray icon does not die with the app
|
||||||
self._tray.show()
|
self._tray.show()
|
||||||
self._ms.show()
|
self._ms.show()
|
||||||
|
|
||||||
# FixMe:
|
# FixMe:
|
||||||
self._log = lambda line: LOG.log(self._oArgs.loglevel,
|
self._log = lambda line: LOG.log(self._args.loglevel,
|
||||||
self._ms.status(line))
|
self._ms.status(line))
|
||||||
# self._ms._log = self._log # was used in callbacks.py
|
# self._ms._log = self._log # was used in callbacks.py
|
||||||
|
|
||||||
if False:
|
if False:
|
||||||
self.status_handler = logging.Handler()
|
self.status_handler = logging.Handler()
|
||||||
self.status_handler.setLevel(logging.INFO) # self._oArgs.loglevel
|
self.status_handler.setLevel(logging.INFO) # self._args.loglevel
|
||||||
self.status_handler.handle = self._ms.status
|
self.status_handler.handle = self._ms.status
|
||||||
|
|
||||||
self._init_callbacks()
|
self._init_callbacks()
|
||||||
@ -789,18 +804,18 @@ class App:
|
|||||||
|
|
||||||
def _create_tox(self, data, settings_):
|
def _create_tox(self, data, settings_):
|
||||||
LOG.info("_create_tox calling tox_factory")
|
LOG.info("_create_tox calling tox_factory")
|
||||||
assert self._oArgs
|
assert self._args
|
||||||
retval = tox_factory(data=data, settings=settings_,
|
retval = tox_factory(data=data, settings=settings_,
|
||||||
args=self._oArgs, app=self)
|
args=self._args, app=self)
|
||||||
LOG.debug("_create_tox succeeded")
|
LOG.debug("_create_tox succeeded")
|
||||||
self._tox = retval
|
self._tox = retval
|
||||||
return retval
|
return retval
|
||||||
|
|
||||||
def _force_exit(self, retval=0):
|
def _force_exit(self, retval=0) -> None:
|
||||||
LOG.debug("_force_exit")
|
LOG.debug("_force_exit")
|
||||||
sys.exit(0)
|
sys.exit(0)
|
||||||
|
|
||||||
def _init_callbacks(self, ms=None):
|
def _init_callbacks(self, ms=None) -> None:
|
||||||
LOG.debug("_init_callbacks")
|
LOG.debug("_init_callbacks")
|
||||||
# this will block if you are not connected
|
# this will block if you are not connected
|
||||||
callbacks.init_callbacks(self._tox, self._profile, self._settings,
|
callbacks.init_callbacks(self._tox, self._profile, self._settings,
|
||||||
@ -811,48 +826,49 @@ class App:
|
|||||||
self._messenger, self._groups_service,
|
self._messenger, self._groups_service,
|
||||||
self._contacts_provider, self._ms)
|
self._contacts_provider, self._ms)
|
||||||
|
|
||||||
def _init_profile(self):
|
def _init_profile(self) -> None:
|
||||||
LOG.debug("_init_profile")
|
LOG.debug("_init_profile")
|
||||||
if not self._profile.has_avatar():
|
if not self._profile.has_avatar():
|
||||||
self._profile.reset_avatar(self._settings['identicons'])
|
self._profile.reset_avatar(self._settings['identicons'])
|
||||||
|
|
||||||
def _kill_toxav(self):
|
def _kill_toxav(self) -> None:
|
||||||
# LOG_debug("_kill_toxav")
|
# LOG_debug("_kill_toxav")
|
||||||
self._calls_manager.set_toxav(None)
|
self._calls_manager.set_toxav(None)
|
||||||
self._tox.AV.kill()
|
self._tox.AV.kill()
|
||||||
|
|
||||||
def _kill_tox(self):
|
def _kill_tox(self) -> None:
|
||||||
# LOG.debug("_kill_tox")
|
# LOG.debug("_kill_tox")
|
||||||
self._tox.kill()
|
self._tox.kill()
|
||||||
|
|
||||||
def loop(self, n):
|
def loop(self, n) -> None:
|
||||||
"""
|
"""
|
||||||
Im guessings - there are 3 sleeps - time, tox, and Qt
|
Im guessing - there are 4 sleeps - time, tox, and Qt gevent
|
||||||
"""
|
"""
|
||||||
interval = self._tox.iteration_interval()
|
interval = self._tox.iteration_interval()
|
||||||
for i in range(n):
|
for i in range(n):
|
||||||
self._tox.iterate()
|
self._tox.iterate()
|
||||||
QtCore.QThread.msleep(interval)
|
#? QtCore.QThread.msleep(interval)
|
||||||
# NO QtCore.QCoreApplication.processEvents()
|
# Cooperative yield, allow gevent to monitor file handles via libevent
|
||||||
sleep(interval / 1000.0)
|
gevent.sleep(interval / 1000.0)
|
||||||
|
# NO?
|
||||||
|
QtCore.QCoreApplication.processEvents()
|
||||||
|
|
||||||
def _test_tox(self):
|
def _test_tox(self) -> None:
|
||||||
self.test_net()
|
self.test_net(iMax=8)
|
||||||
self._ms.log_console()
|
self._ms.log_console()
|
||||||
|
|
||||||
def test_net(self, lElts=None, oThread=None, iMax=4):
|
def test_net(self, lElts=None, oThread=None, iMax=4) -> None:
|
||||||
|
|
||||||
LOG.debug("test_net " +self._oArgs.network)
|
|
||||||
# bootstrap
|
# bootstrap
|
||||||
LOG.debug('Calling generate_nodes: udp')
|
LOG.debug('test_net: Calling generate_nodes: udp')
|
||||||
lNodes = ts.generate_nodes(oArgs=self._oArgs,
|
lNodes = ts.generate_nodes(oArgs=self._args,
|
||||||
ipv='ipv4',
|
ipv='ipv4',
|
||||||
udp_not_tcp=True)
|
udp_not_tcp=True)
|
||||||
self._settings['current_nodes_udp'] = lNodes
|
self._settings['current_nodes_udp'] = lNodes
|
||||||
if not lNodes:
|
if not lNodes:
|
||||||
LOG.warn('empty generate_nodes udp')
|
LOG.warn('empty generate_nodes udp')
|
||||||
LOG.debug('Calling generate_nodes: tcp')
|
LOG.debug('test_net: Calling generate_nodes: tcp')
|
||||||
lNodes = ts.generate_nodes(oArgs=self._oArgs,
|
lNodes = ts.generate_nodes(oArgs=self._args,
|
||||||
ipv='ipv4',
|
ipv='ipv4',
|
||||||
udp_not_tcp=False)
|
udp_not_tcp=False)
|
||||||
self._settings['current_nodes_tcp'] = lNodes
|
self._settings['current_nodes_tcp'] = lNodes
|
||||||
@ -860,8 +876,8 @@ class App:
|
|||||||
LOG.warn('empty generate_nodes tcp')
|
LOG.warn('empty generate_nodes tcp')
|
||||||
|
|
||||||
# if oThread and oThread._stop_thread: return
|
# if oThread and oThread._stop_thread: return
|
||||||
LOG.debug("test_net network=" +self._oArgs.network +' iMax=' +str(iMax))
|
LOG.debug("test_net network=" +self._args.network +' iMax=' +str(iMax))
|
||||||
if self._oArgs.network not in ['local', 'localnew', 'newlocal']:
|
if self._args.network not in ['local', 'localnew', 'newlocal']:
|
||||||
b = ts.bAreWeConnected()
|
b = ts.bAreWeConnected()
|
||||||
if b is None:
|
if b is None:
|
||||||
i = os.system('ip route|grep ^def')
|
i = os.system('ip route|grep ^def')
|
||||||
@ -870,33 +886,33 @@ class App:
|
|||||||
else:
|
else:
|
||||||
b = True
|
b = True
|
||||||
if not b:
|
if not b:
|
||||||
LOG.warn("No default route for network " +self._oArgs.network)
|
LOG.warn("No default route for network " +self._args.network)
|
||||||
text = 'You have no default route - are you connected?'
|
text = 'You have no default route - are you connected?'
|
||||||
reply = util_ui.question(text, "Are you connected?")
|
reply = util_ui.question(text, "Are you connected?")
|
||||||
if not reply: return
|
if not reply: return
|
||||||
iMax = 1
|
iMax = 1
|
||||||
else:
|
else:
|
||||||
LOG.debug("Have default route for network " +self._oArgs.network)
|
LOG.debug("Have default route for network " +self._args.network)
|
||||||
|
|
||||||
lUdpElts = self._settings['current_nodes_udp']
|
lUdpElts = self._settings['current_nodes_udp']
|
||||||
if self._oArgs.proxy_type <= 0 and not lUdpElts:
|
if self._args.proxy_type <= 0 and not lUdpElts:
|
||||||
title = 'test_net Error'
|
title = 'test_net Error'
|
||||||
text = 'Error: ' + str('No UDP nodes')
|
text = 'Error: ' + str('No UDP nodes')
|
||||||
util_ui.message_box(text, title)
|
util_ui.message_box(text, title)
|
||||||
return
|
return
|
||||||
lTcpElts = self._settings['current_nodes_tcp']
|
lTcpElts = self._settings['current_nodes_tcp']
|
||||||
if self._oArgs.proxy_type > 0 and not lTcpElts:
|
if self._args.proxy_type > 0 and not lTcpElts:
|
||||||
title = 'test_net Error'
|
title = 'test_net Error'
|
||||||
text = 'Error: ' + str('No TCP nodes')
|
text = 'Error: ' + str('No TCP nodes')
|
||||||
util_ui.message_box(text, title)
|
util_ui.message_box(text, title)
|
||||||
return
|
return
|
||||||
LOG.debug(f"test_net {self._oArgs.network} lenU={len(lUdpElts)} lenT={len(lTcpElts)} iMax= {iMax}")
|
LOG.debug(f"test_net {self._args.network} lenU={len(lUdpElts)} lenT={len(lTcpElts)} iMax={iMax}")
|
||||||
i = 0
|
i = 0
|
||||||
while i < iMax:
|
while i < iMax:
|
||||||
# if oThread and oThread._stop_thread: return
|
# if oThread and oThread._stop_thread: return
|
||||||
i = i + 1
|
i = i + 1
|
||||||
LOG.debug(f"bootstrapping status proxy={self._oArgs.proxy_type} # {i}")
|
LOG.debug(f"bootstrapping status proxy={self._args.proxy_type} # {i}")
|
||||||
if self._oArgs.proxy_type == 0:
|
if self._args.proxy_type == 0:
|
||||||
self._test_bootstrap(lUdpElts)
|
self._test_bootstrap(lUdpElts)
|
||||||
else:
|
else:
|
||||||
self._test_bootstrap([lUdpElts[0]])
|
self._test_bootstrap([lUdpElts[0]])
|
||||||
@ -906,10 +922,9 @@ class App:
|
|||||||
if status > 0:
|
if status > 0:
|
||||||
LOG.info(f"Connected # {i}" +' : ' +repr(status))
|
LOG.info(f"Connected # {i}" +' : ' +repr(status))
|
||||||
break
|
break
|
||||||
LOG.trace(f"Connected status #{i}: {status!r}")
|
LOG.trace(f"Connected status #{i}: {status}")
|
||||||
self.loop(2)
|
|
||||||
|
|
||||||
def _test_env(self):
|
def _test_env(self) -> None:
|
||||||
_settings = self._settings
|
_settings = self._settings
|
||||||
if 'proxy_type' not in _settings or _settings['proxy_type'] == 0 or \
|
if 'proxy_type' not in _settings or _settings['proxy_type'] == 0 or \
|
||||||
not _settings['proxy_host'] or not _settings['proxy_port']:
|
not _settings['proxy_host'] or not _settings['proxy_port']:
|
||||||
@ -932,7 +947,7 @@ class App:
|
|||||||
# LOG.debug(f"test_env {len(lElts)}")
|
# LOG.debug(f"test_env {len(lElts)}")
|
||||||
return env
|
return env
|
||||||
|
|
||||||
def _test_bootstrap(self, lElts=None):
|
def _test_bootstrap(self, lElts=None) -> None:
|
||||||
if lElts is None:
|
if lElts is None:
|
||||||
lElts = self._settings['current_nodes_udp']
|
lElts = self._settings['current_nodes_udp']
|
||||||
LOG.debug(f"_test_bootstrap #Elts={len(lElts)}")
|
LOG.debug(f"_test_bootstrap #Elts={len(lElts)}")
|
||||||
@ -942,14 +957,14 @@ class App:
|
|||||||
ts.bootstrap_udp(lElts[:iNODES], [self._tox])
|
ts.bootstrap_udp(lElts[:iNODES], [self._tox])
|
||||||
LOG.info("Connected status: " +repr(self._tox.self_get_connection_status()))
|
LOG.info("Connected status: " +repr(self._tox.self_get_connection_status()))
|
||||||
|
|
||||||
def _test_relays(self, lElts=None):
|
def _test_relays(self, lElts=None) -> None:
|
||||||
if lElts is None:
|
if lElts is None:
|
||||||
lElts = self._settings['current_nodes_tcp']
|
lElts = self._settings['current_nodes_tcp']
|
||||||
shuffle(lElts)
|
shuffle(lElts)
|
||||||
LOG.debug(f"_test_relays {len(lElts)}")
|
LOG.debug(f"_test_relays {len(lElts)}")
|
||||||
ts.bootstrap_tcp(lElts[:iNODES], [self._tox])
|
ts.bootstrap_tcp(lElts[:iNODES], [self._tox])
|
||||||
|
|
||||||
def _test_nmap(self, lElts=None):
|
def _test_nmap(self, lElts=None) -> None:
|
||||||
LOG.debug("_test_nmap")
|
LOG.debug("_test_nmap")
|
||||||
if not self._tox: return
|
if not self._tox: return
|
||||||
title = 'Extended Test Suite'
|
title = 'Extended Test Suite'
|
||||||
@ -960,17 +975,18 @@ class App:
|
|||||||
reply = util_ui.question(text, title)
|
reply = util_ui.question(text, title)
|
||||||
if not reply: return
|
if not reply: return
|
||||||
|
|
||||||
|
if self._args.proxy_type == 0:
|
||||||
|
sProt = "udp4"
|
||||||
|
else:
|
||||||
|
sProt = "tcp4"
|
||||||
if lElts is None:
|
if lElts is None:
|
||||||
if self._oArgs.proxy_type == 0:
|
if self._args.proxy_type == 0:
|
||||||
sProt = "udp4"
|
lElts = self._settings['current_nodes_udp']
|
||||||
lElts = self._settings['current_nodes_tcp']
|
|
||||||
else:
|
else:
|
||||||
sProt = "tcp4"
|
|
||||||
lElts = self._settings['current_nodes_tcp']
|
lElts = self._settings['current_nodes_tcp']
|
||||||
shuffle(lElts)
|
shuffle(lElts)
|
||||||
try:
|
try:
|
||||||
ts.bootstrap_iNmapInfo(lElts, self._oArgs, sProt)
|
ts.bootstrap_iNmapInfo(lElts, self._args, sProt)
|
||||||
self._ms.log_console()
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
LOG.error(f"test_nmap ' +' : {e}")
|
LOG.error(f"test_nmap ' +' : {e}")
|
||||||
LOG.error('_test_nmap(): ' \
|
LOG.error('_test_nmap(): ' \
|
||||||
@ -982,18 +998,18 @@ class App:
|
|||||||
# LOG.info("Connected status: " +repr(self._tox.self_get_connection_status()))
|
# LOG.info("Connected status: " +repr(self._tox.self_get_connection_status()))
|
||||||
self._ms.log_console()
|
self._ms.log_console()
|
||||||
|
|
||||||
def _test_main(self):
|
def _test_main(self) -> None:
|
||||||
from tests.tests_socks import main as tests_main
|
from toxygen_toxygen_wrapper.toxygen_wrapper.tests.tests_wrapper import main as tests_main
|
||||||
LOG.debug("_test_main")
|
LOG.debug("_test_main")
|
||||||
if not self._tox: return
|
if not self._tox: return
|
||||||
title = 'Extended Test Suite'
|
title = 'Extended Test Suite'
|
||||||
text = 'Run the Extended Test Suite?\nThe program may freeze for 20-60 minutes.'
|
text = 'Run the Extended Test Suite?\nThe program may freeze for 20-60 minutes.'
|
||||||
reply = util_ui.question(text, title)
|
reply = util_ui.question(text, title)
|
||||||
if reply:
|
if reply:
|
||||||
if hasattr(self._oArgs, 'proxy_type') and self._oArgs.proxy_type:
|
if hasattr(self._args, 'proxy_type') and self._args.proxy_type:
|
||||||
lArgs = ['--proxy_host', self._oArgs.proxy_host,
|
lArgs = ['--proxy_host', self._args.proxy_host,
|
||||||
'--proxy_port', str(self._oArgs.proxy_port),
|
'--proxy_port', str(self._args.proxy_port),
|
||||||
'--proxy_type', str(self._oArgs.proxy_type), ]
|
'--proxy_type', str(self._args.proxy_type), ]
|
||||||
else:
|
else:
|
||||||
lArgs = list()
|
lArgs = list()
|
||||||
try:
|
try:
|
||||||
@ -1007,6 +1023,7 @@ class App:
|
|||||||
util_ui.message_box(text, title)
|
util_ui.message_box(text, title)
|
||||||
self._ms.log_console()
|
self._ms.log_console()
|
||||||
|
|
||||||
|
#? unused
|
||||||
class GEventProcessing:
|
class GEventProcessing:
|
||||||
"""Interoperability class between Qt/gevent that allows processing gevent
|
"""Interoperability class between Qt/gevent that allows processing gevent
|
||||||
tasks during Qt idle periods."""
|
tasks during Qt idle periods."""
|
||||||
@ -1019,12 +1036,15 @@ class GEventProcessing:
|
|||||||
self._timer = QTimer()
|
self._timer = QTimer()
|
||||||
self._timer.timeout.connect(self.process_events)
|
self._timer.timeout.connect(self.process_events)
|
||||||
self._timer.start(0)
|
self._timer.start(0)
|
||||||
def __enter__(self):
|
def __enter__(self) -> None:
|
||||||
pass
|
pass
|
||||||
def __exit__(self, *exc_info):
|
|
||||||
|
def __exit__(self, *exc_info) -> None:
|
||||||
self._timer.stop()
|
self._timer.stop()
|
||||||
def process_events(self, idle_period=None):
|
|
||||||
|
def process_events(self, idle_period=None) -> None:
|
||||||
if idle_period is None:
|
if idle_period is None:
|
||||||
idle_period = self._idle_period
|
idle_period = self._idle_period
|
||||||
# Cooperative yield, allow gevent to monitor file handles via libevent
|
# Cooperative yield, allow gevent to monitor file handles via libevent
|
||||||
gevent.sleep(idle_period)
|
gevent.sleep(idle_period)
|
||||||
|
#? QtCore.QCoreApplication.processEvents()
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
|
# -*- mode: python; indent-tabs-mode: nil; py-indent-offset: 4; coding: utf-8 -*-
|
||||||
|
|
||||||
class Call:
|
class Call:
|
||||||
|
|
||||||
@ -17,9 +17,7 @@ class Call:
|
|||||||
|
|
||||||
is_active = property(get_is_active, set_is_active)
|
is_active = property(get_is_active, set_is_active)
|
||||||
|
|
||||||
# -----------------------------------------------------------------------------------------------------------------
|
|
||||||
# Audio
|
# Audio
|
||||||
# -----------------------------------------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
def get_in_audio(self):
|
def get_in_audio(self):
|
||||||
return self._in_audio
|
return self._in_audio
|
||||||
@ -37,9 +35,7 @@ class Call:
|
|||||||
|
|
||||||
out_audio = property(get_out_audio, set_out_audio)
|
out_audio = property(get_out_audio, set_out_audio)
|
||||||
|
|
||||||
# -----------------------------------------------------------------------------------------------------------------
|
|
||||||
# Video
|
# Video
|
||||||
# -----------------------------------------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
def get_in_video(self):
|
def get_in_video(self):
|
||||||
return self._in_video
|
return self._in_video
|
||||||
|
@ -1,40 +1,66 @@
|
|||||||
# -*- mode: python; indent-tabs-mode: nil; py-indent-offset: 4; coding: utf-8 -*-
|
# -*- mode: python; indent-tabs-mode: nil; py-indent-offset: 4; coding: utf-8 -*-
|
||||||
import pyaudio
|
|
||||||
import time
|
import time
|
||||||
import threading
|
import threading
|
||||||
|
import logging
|
||||||
import itertools
|
import itertools
|
||||||
|
|
||||||
from wrapper.toxav_enums import *
|
from toxygen_wrapper.toxav_enums import *
|
||||||
|
from toxygen_wrapper.tests import support_testing as ts
|
||||||
|
from toxygen_wrapper.tests.support_testing import LOG_ERROR, LOG_WARN, LOG_INFO, LOG_DEBUG, LOG_TRACE
|
||||||
|
|
||||||
|
with ts.ignoreStderr():
|
||||||
|
import pyaudio
|
||||||
from av import screen_sharing
|
from av import screen_sharing
|
||||||
from av.call import Call
|
from av.call import Call
|
||||||
import common.tox_save
|
import common.tox_save
|
||||||
|
from middleware.threads import BaseQThread
|
||||||
|
|
||||||
from utils import ui as util_ui
|
from utils import ui as util_ui
|
||||||
import wrapper_tests.support_testing as ts
|
|
||||||
from middleware.threads import invoke_in_main_thread
|
from middleware.threads import invoke_in_main_thread
|
||||||
from main import sleep
|
# from middleware.threads import BaseThread
|
||||||
from middleware.threads import BaseThread
|
|
||||||
|
sleep = time.sleep
|
||||||
|
|
||||||
global LOG
|
global LOG
|
||||||
import logging
|
|
||||||
LOG = logging.getLogger('app.'+__name__)
|
LOG = logging.getLogger('app.'+__name__)
|
||||||
# callbacks can be called in any thread so were being careful
|
|
||||||
def LOG_ERROR(l): print('EROR< '+l)
|
|
||||||
def LOG_WARN(l): print('WARN< '+l)
|
|
||||||
def LOG_INFO(l):
|
|
||||||
bIsVerbose = hasattr(__builtins__, 'app') and app.oArgs.loglevel <= 20-1
|
|
||||||
if bIsVerbose: print('INFO< '+l)
|
|
||||||
def LOG_DEBUG(l):
|
|
||||||
bIsVerbose = hasattr(__builtins__, 'app') and app.oArgs.loglevel <= 10-1
|
|
||||||
if bIsVerbose: print('DBUG< '+l)
|
|
||||||
def LOG_TRACE(l):
|
|
||||||
bIsVerbose = hasattr(__builtins__, 'app') and app.oArgs.loglevel < 10-1
|
|
||||||
pass # print('TRACE+ '+l)
|
|
||||||
|
|
||||||
TIMER_TIMEOUT = 30.0
|
TIMER_TIMEOUT = 30.0
|
||||||
bSTREAM_CALLBACK = False
|
|
||||||
iFPS = 25
|
iFPS = 25
|
||||||
|
|
||||||
|
class AudioThread(BaseQThread):
|
||||||
|
def __init__(self, av, name=''):
|
||||||
|
super().__init__()
|
||||||
|
self.av = av
|
||||||
|
self._name = name
|
||||||
|
|
||||||
|
def join(self, ito=ts.iTHREAD_TIMEOUT):
|
||||||
|
LOG_DEBUG(f"AudioThread join {self}")
|
||||||
|
# dunno
|
||||||
|
|
||||||
|
def run(self) -> None:
|
||||||
|
LOG_DEBUG('AudioThread run: ')
|
||||||
|
# maybe not needed
|
||||||
|
while not self._stop_thread:
|
||||||
|
self.av.send_audio()
|
||||||
|
sleep(100.0 / 1000.0)
|
||||||
|
|
||||||
|
class VideoThread(BaseQThread):
|
||||||
|
def __init__(self, av, name=''):
|
||||||
|
super().__init__()
|
||||||
|
self.av = av
|
||||||
|
self._name = name
|
||||||
|
|
||||||
|
def join(self, ito=ts.iTHREAD_TIMEOUT):
|
||||||
|
LOG_DEBUG(f"VideoThread join {self}")
|
||||||
|
# dunno
|
||||||
|
|
||||||
|
def run(self) -> None:
|
||||||
|
LOG_DEBUG('VideoThread run: ')
|
||||||
|
# maybe not needed
|
||||||
|
while not self._stop_thread:
|
||||||
|
self.av.send_video()
|
||||||
|
sleep(100.0 / 1000.0)
|
||||||
|
|
||||||
class AV(common.tox_save.ToxAvSave):
|
class AV(common.tox_save.ToxAvSave):
|
||||||
|
|
||||||
def __init__(self, toxav, settings):
|
def __init__(self, toxav, settings):
|
||||||
@ -45,10 +71,10 @@ class AV(common.tox_save.ToxAvSave):
|
|||||||
s = settings
|
s = settings
|
||||||
if 'video' not in s:
|
if 'video' not in s:
|
||||||
LOG.warn("AV.__init__ 'video' not in s" )
|
LOG.warn("AV.__init__ 'video' not in s" )
|
||||||
LOG.debug(f"AV.__init__ {s!r}" )
|
LOG.debug(f"AV.__init__ {s}" )
|
||||||
elif 'device' not in s['video']:
|
elif 'device' not in s['video']:
|
||||||
LOG.warn("AV.__init__ 'device' not in s.video" )
|
LOG.warn("AV.__init__ 'device' not in s.video" )
|
||||||
LOG.debug(f"AV.__init__ {s['video']!r}" )
|
LOG.debug(f"AV.__init__ {s['video']}" )
|
||||||
|
|
||||||
self._calls = {} # dict: key - friend number, value - Call instance
|
self._calls = {} # dict: key - friend number, value - Call instance
|
||||||
|
|
||||||
@ -80,20 +106,20 @@ class AV(common.tox_save.ToxAvSave):
|
|||||||
self.lPaSampleratesI = ts.lSdSamplerates(iInput)
|
self.lPaSampleratesI = ts.lSdSamplerates(iInput)
|
||||||
iOutput = self._settings['audio']['output']
|
iOutput = self._settings['audio']['output']
|
||||||
self.lPaSampleratesO = ts.lSdSamplerates(iOutput)
|
self.lPaSampleratesO = ts.lSdSamplerates(iOutput)
|
||||||
|
|
||||||
global oPYA
|
global oPYA
|
||||||
oPYA = self._audio = pyaudio.PyAudio()
|
oPYA = self._audio = pyaudio.PyAudio()
|
||||||
|
|
||||||
def stop(self):
|
def stop(self) -> None:
|
||||||
|
LOG_DEBUG(f"AV.CA stop {self._video_thread}")
|
||||||
self._running = False
|
self._running = False
|
||||||
self.stop_audio_thread()
|
self.stop_audio_thread()
|
||||||
self.stop_video_thread()
|
self.stop_video_thread()
|
||||||
|
|
||||||
def __contains__(self, friend_number):
|
def __contains__(self, friend_number:int) -> bool:
|
||||||
return friend_number in self._calls
|
return friend_number in self._calls
|
||||||
|
|
||||||
# -----------------------------------------------------------------------------------------------------------------
|
|
||||||
# Calls
|
# Calls
|
||||||
# -----------------------------------------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
def __call__(self, friend_number, audio, video):
|
def __call__(self, friend_number, audio, video):
|
||||||
"""Call friend with specified number"""
|
"""Call friend with specified number"""
|
||||||
@ -107,7 +133,7 @@ class AV(common.tox_save.ToxAvSave):
|
|||||||
self._toxav.call(friend_number,
|
self._toxav.call(friend_number,
|
||||||
self._audio_krate_tox_audio if audio else 0,
|
self._audio_krate_tox_audio if audio else 0,
|
||||||
self._audio_krate_tox_video if video 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}")
|
LOG.warn(f"_toxav.call already has {friend_number}")
|
||||||
return
|
return
|
||||||
self._calls[friend_number] = Call(audio, video)
|
self._calls[friend_number] = Call(audio, video)
|
||||||
@ -116,11 +142,12 @@ class AV(common.tox_save.ToxAvSave):
|
|||||||
|
|
||||||
def accept_call(self, friend_number, audio_enabled, video_enabled):
|
def accept_call(self, friend_number, audio_enabled, video_enabled):
|
||||||
# obsolete
|
# obsolete
|
||||||
return call_accept_call(self, friend_number, audio_enabled, video_enabled)
|
self.call_accept_call(friend_number, audio_enabled, video_enabled)
|
||||||
|
|
||||||
def call_accept_call(self, friend_number, audio_enabled, video_enabled):
|
def call_accept_call(self, friend_number, audio_enabled, video_enabled) -> None:
|
||||||
LOG.debug(f"call_accept_call from {friend_number} {self._running}" +
|
# called from CM.accept_call in a try:
|
||||||
f"{audio_enabled} {video_enabled}")
|
LOG.debug(f"call_accept_call from F={friend_number} R={self._running}" +
|
||||||
|
f" A={audio_enabled} V={video_enabled}")
|
||||||
# import pdb; pdb.set_trace() - gets into q Qt exec_ problem
|
# import pdb; pdb.set_trace() - gets into q Qt exec_ problem
|
||||||
# ts.trepan_handler()
|
# ts.trepan_handler()
|
||||||
|
|
||||||
@ -134,21 +161,19 @@ class AV(common.tox_save.ToxAvSave):
|
|||||||
self._toxav.answer(friend_number,
|
self._toxav.answer(friend_number,
|
||||||
self._audio_krate_tox_audio if audio_enabled else 0,
|
self._audio_krate_tox_audio if audio_enabled else 0,
|
||||||
self._audio_krate_tox_video if video_enabled else 0)
|
self._audio_krate_tox_video if video_enabled else 0)
|
||||||
except ArgumentError as e:
|
except Exception as e:
|
||||||
LOG.debug(f"AV accept_call error from {friend_number} {self._running}" +
|
LOG.error(f"AV accept_call error from {friend_number} {self._running} {e}")
|
||||||
f"{e}")
|
|
||||||
raise
|
raise
|
||||||
if audio_enabled:
|
|
||||||
# may raise
|
|
||||||
self.start_audio_thread()
|
|
||||||
if video_enabled:
|
if video_enabled:
|
||||||
# may raise
|
# may raise
|
||||||
self.start_video_thread()
|
self.start_video_thread()
|
||||||
|
if audio_enabled:
|
||||||
|
LOG.debug(f"calls accept_call calling start_audio_thread F={friend_number}")
|
||||||
|
# may raise
|
||||||
|
self.start_audio_thread()
|
||||||
|
|
||||||
def finish_call(self, friend_number, by_friend=False):
|
def finish_call(self, friend_number, by_friend=False) -> None:
|
||||||
LOG.debug(f"finish_call {friend_number}")
|
LOG.debug(f"finish_call {friend_number}")
|
||||||
if not by_friend:
|
|
||||||
self._toxav.call_control(friend_number, TOXAV_CALL_CONTROL['CANCEL'])
|
|
||||||
if friend_number in self._calls:
|
if friend_number in self._calls:
|
||||||
del self._calls[friend_number]
|
del self._calls[friend_number]
|
||||||
try:
|
try:
|
||||||
@ -162,14 +187,18 @@ class AV(common.tox_save.ToxAvSave):
|
|||||||
# dunno
|
# dunno
|
||||||
self.stop_audio_thread()
|
self.stop_audio_thread()
|
||||||
self.stop_video_thread()
|
self.stop_video_thread()
|
||||||
|
if not by_friend:
|
||||||
|
LOG.debug(f"finish_call before call_control {friend_number}")
|
||||||
|
self._toxav.call_control(friend_number, TOXAV_CALL_CONTROL['CANCEL'])
|
||||||
|
LOG.debug(f"finish_call after call_control {friend_number}")
|
||||||
|
|
||||||
def finish_not_started_call(self, friend_number):
|
def finish_not_started_call(self, friend_number:int) -> None:
|
||||||
if friend_number in self:
|
if friend_number in self:
|
||||||
call = self._calls[friend_number]
|
call = self._calls[friend_number]
|
||||||
if not call.is_active:
|
if not call.is_active:
|
||||||
self.finish_call(friend_number)
|
self.finish_call(friend_number)
|
||||||
|
|
||||||
def toxav_call_state_cb(self, friend_number, state):
|
def toxav_call_state_cb(self, friend_number, state) -> None:
|
||||||
"""
|
"""
|
||||||
New call state
|
New call state
|
||||||
"""
|
"""
|
||||||
@ -186,18 +215,17 @@ class AV(common.tox_save.ToxAvSave):
|
|||||||
if state | TOXAV_FRIEND_CALL_STATE['ACCEPTING_V'] and call.out_video:
|
if state | TOXAV_FRIEND_CALL_STATE['ACCEPTING_V'] and call.out_video:
|
||||||
self.start_video_thread()
|
self.start_video_thread()
|
||||||
|
|
||||||
def is_video_call(self, number):
|
def is_video_call(self, number) -> bool:
|
||||||
return number in self and self._calls[number].in_video
|
return number in self and self._calls[number].in_video
|
||||||
|
|
||||||
# -----------------------------------------------------------------------------------------------------------------
|
|
||||||
# Threads
|
# Threads
|
||||||
# -----------------------------------------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
def start_audio_thread(self):
|
def start_audio_thread(self, bSTREAM_CALLBACK=False) -> None:
|
||||||
"""
|
"""
|
||||||
Start audio sending
|
Start audio sending
|
||||||
from a callback
|
from a callback
|
||||||
"""
|
"""
|
||||||
|
# called from call_accept_call in an try: from CM.accept_call
|
||||||
global oPYA
|
global oPYA
|
||||||
# was iInput = self._settings._args.audio['input']
|
# was iInput = self._settings._args.audio['input']
|
||||||
iInput = self._settings['audio']['input']
|
iInput = self._settings['audio']['input']
|
||||||
@ -205,29 +233,39 @@ class AV(common.tox_save.ToxAvSave):
|
|||||||
LOG_WARN(f"start_audio_thread device={iInput}")
|
LOG_WARN(f"start_audio_thread device={iInput}")
|
||||||
return
|
return
|
||||||
LOG_DEBUG(f"start_audio_thread device={iInput}")
|
LOG_DEBUG(f"start_audio_thread device={iInput}")
|
||||||
lPaSamplerates = ts.lSdSamplerates(iInput)
|
lPaSamplerates = ts.lSdSamplerates(iInput)
|
||||||
if not(len(lPaSamplerates)):
|
if not(len(lPaSamplerates)):
|
||||||
e = f"No supported sample rates for device: audio[input]={iInput!r}"
|
e = f"No sample rates for device: audio[input]={iInput}"
|
||||||
LOG_ERROR(f"start_audio_thread {e}")
|
LOG_WARN(f"start_audio_thread {e}")
|
||||||
#?? dunno - cancel call?
|
#?? dunno - cancel call? - no let the user do it
|
||||||
return
|
# return
|
||||||
if not self._audio_rate_pa in lPaSamplerates:
|
# just guessing here in case that's a false negative
|
||||||
LOG_WARN(f"{self._audio_rate_pa} not in {lPaSamplerates!r}")
|
lPaSamplerates = [round(oPYA.get_device_info_by_index(iInput)['defaultSampleRate'])]
|
||||||
if False:
|
if lPaSamplerates and self._audio_rate_pa in lPaSamplerates:
|
||||||
self._audio_rate_pa = oPYA.get_device_info_by_index(iInput)['defaultSampleRate']
|
pass
|
||||||
else:
|
elif lPaSamplerates:
|
||||||
LOG_WARN(f"Setting audio_rate to: {lPaSamplerates[0]}")
|
LOG_WARN(f"Setting audio_rate to: {lPaSamplerates[0]}")
|
||||||
self._audio_rate_pa = lPaSamplerates[0]
|
self._audio_rate_pa = lPaSamplerates[0]
|
||||||
|
elif 'defaultSampleRate' in oPYA.get_device_info_by_index(iInput):
|
||||||
|
self._audio_rate_pa = oPYA.get_device_info_by_index(iInput)['defaultSampleRate']
|
||||||
|
LOG_WARN(f"setting to defaultSampleRate")
|
||||||
|
else:
|
||||||
|
LOG_WARN(f"{self._audio_rate_pa} not in {lPaSamplerates}")
|
||||||
|
# a float is in here - must it be int?
|
||||||
|
if type(self._audio_rate_pa) == float:
|
||||||
|
self._audio_rate_pa = round(self._audio_rate_pa)
|
||||||
try:
|
try:
|
||||||
LOG_DEBUG( f"start_audio_thread framerate: {self._audio_rate_pa}" \
|
|
||||||
+f" device: {iInput}"
|
|
||||||
+f" supported: {lPaSamplerates!r}")
|
|
||||||
if self._audio_rate_pa not in lPaSamplerates:
|
if self._audio_rate_pa not in lPaSamplerates:
|
||||||
LOG_WARN(f"PAudio sampling rate was {self._audio_rate_pa} changed to {lPaSamplerates[0]}")
|
LOG_WARN(f"PAudio sampling rate was {self._audio_rate_pa} changed to {lPaSamplerates[0]}")
|
||||||
|
LOG_DEBUG(f"lPaSamplerates={lPaSamplerates}")
|
||||||
self._audio_rate_pa = lPaSamplerates[0]
|
self._audio_rate_pa = lPaSamplerates[0]
|
||||||
|
else:
|
||||||
|
LOG_DEBUG( f"start_audio_thread framerate: {self._audio_rate_pa}" \
|
||||||
|
+f" device: {iInput}"
|
||||||
|
+f" supported: {lPaSamplerates}")
|
||||||
|
|
||||||
if bSTREAM_CALLBACK:
|
if bSTREAM_CALLBACK:
|
||||||
|
# why would you not call a thread?
|
||||||
self._audio_stream = oPYA.open(format=pyaudio.paInt16,
|
self._audio_stream = oPYA.open(format=pyaudio.paInt16,
|
||||||
rate=self._audio_rate_pa,
|
rate=self._audio_rate_pa,
|
||||||
channels=self._audio_channels,
|
channels=self._audio_channels,
|
||||||
@ -241,8 +279,8 @@ class AV(common.tox_save.ToxAvSave):
|
|||||||
sleep(0.1)
|
sleep(0.1)
|
||||||
self._audio_stream.stop_stream()
|
self._audio_stream.stop_stream()
|
||||||
self._audio_stream.close()
|
self._audio_stream.close()
|
||||||
|
|
||||||
else:
|
else:
|
||||||
|
LOG_DEBUG( f"start_audio_thread starting thread {self._audio_rate_pa}")
|
||||||
self._audio_stream = oPYA.open(format=pyaudio.paInt16,
|
self._audio_stream = oPYA.open(format=pyaudio.paInt16,
|
||||||
rate=self._audio_rate_pa,
|
rate=self._audio_rate_pa,
|
||||||
channels=self._audio_channels,
|
channels=self._audio_channels,
|
||||||
@ -250,30 +288,35 @@ class AV(common.tox_save.ToxAvSave):
|
|||||||
input_device_index=iInput,
|
input_device_index=iInput,
|
||||||
frames_per_buffer=self._audio_sample_count_pa * 10)
|
frames_per_buffer=self._audio_sample_count_pa * 10)
|
||||||
self._audio_running = True
|
self._audio_running = True
|
||||||
self._audio_thread = BaseThread(target=self.send_audio,
|
self._audio_thread = AudioThread(self,
|
||||||
name='_audio_thread')
|
name='_audio_thread')
|
||||||
self._audio_thread.start()
|
self._audio_thread.start()
|
||||||
|
LOG_DEBUG( f"start_audio_thread started thread name='_audio_thread'")
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
LOG.error(f"Starting self._audio.open {e}")
|
LOG_ERROR(f"Starting self._audio.open {e}")
|
||||||
LOG.debug(repr(dict(format=pyaudio.paInt16,
|
LOG_DEBUG(repr(dict(format=pyaudio.paInt16,
|
||||||
rate=self._audio_rate_pa,
|
rate=self._audio_rate_pa,
|
||||||
channels=self._audio_channels,
|
channels=self._audio_channels,
|
||||||
input=True,
|
input=True,
|
||||||
input_device_index=iInput,
|
input_device_index=iInput,
|
||||||
frames_per_buffer=self._audio_sample_count_pa * 10)))
|
frames_per_buffer=self._audio_sample_count_pa * 10)))
|
||||||
# catcher in place in calls_manager? not if from a callback
|
# catcher in place in calls_manager? yes accept_call
|
||||||
# calls_manager._call.toxav_call_state_cb(friend_number, mask)
|
# calls_manager._call.toxav_call_state_cb(friend_number, mask)
|
||||||
# raise RuntimeError(e)
|
invoke_in_main_thread(util_ui.message_box,
|
||||||
|
str(e),
|
||||||
|
util_ui.tr("Starting self._audio.open"))
|
||||||
return
|
return
|
||||||
else:
|
else:
|
||||||
LOG_DEBUG(f"start_audio_thread {self._audio_stream!r}")
|
LOG_DEBUG(f"start_audio_thread {self._audio_stream}")
|
||||||
|
|
||||||
def stop_audio_thread(self):
|
def stop_audio_thread(self) -> None:
|
||||||
|
LOG_DEBUG(f"stop_audio_thread {self._audio_stream}")
|
||||||
|
|
||||||
if self._audio_thread is None:
|
if self._audio_thread is None:
|
||||||
return
|
return
|
||||||
self._audio_running = False
|
self._audio_running = False
|
||||||
|
self._audio_thread._stop_thread = True
|
||||||
|
|
||||||
self._audio_thread = None
|
self._audio_thread = None
|
||||||
self._audio_stream = None
|
self._audio_stream = None
|
||||||
@ -284,30 +327,29 @@ class AV(common.tox_save.ToxAvSave):
|
|||||||
self._out_stream.close()
|
self._out_stream.close()
|
||||||
self._out_stream = None
|
self._out_stream = None
|
||||||
|
|
||||||
def start_video_thread(self):
|
def start_video_thread(self) -> None:
|
||||||
if self._video_thread is not None:
|
if self._video_thread is not None:
|
||||||
return
|
return
|
||||||
s = self._settings
|
s = self._settings
|
||||||
if 'video' not in s:
|
if 'video' not in s:
|
||||||
LOG.warn("AV.__init__ 'video' not in s" )
|
LOG.warn("AV.__init__ 'video' not in s" )
|
||||||
LOG.debug(f"start_video_thread {s!r}" )
|
LOG.debug(f"start_video_thread {s}" )
|
||||||
raise RuntimeError("start_video_thread not 'video' in s)" )
|
raise RuntimeError("start_video_thread not 'video' in s)" )
|
||||||
elif 'device' not in s['video']:
|
if 'device' not in s['video']:
|
||||||
LOG.error("start_video_thread not 'device' in s['video']" )
|
LOG.error("start_video_thread not 'device' in s['video']" )
|
||||||
LOG.debug(f"start_video_thread {s['video']!r}" )
|
LOG.debug(f"start_video_thread {s['video']}" )
|
||||||
raise RuntimeError("start_video_thread not 'device' ins s['video']" )
|
raise RuntimeError("start_video_thread not 'device' ins s['video']" )
|
||||||
self._video_width = s['video']['width']
|
self._video_width = s['video']['width']
|
||||||
self._video_height = s['video']['height']
|
self._video_height = s['video']['height']
|
||||||
|
|
||||||
# dunno
|
# dunno
|
||||||
if True or s['video']['device'] == -1:
|
if s['video']['device'] == -1:
|
||||||
self._video = screen_sharing.DesktopGrabber(s['video']['x'],
|
self._video = screen_sharing.DesktopGrabber(s['video']['x'],
|
||||||
s['video']['y'],
|
s['video']['y'],
|
||||||
s['video']['width'],
|
s['video']['width'],
|
||||||
s['video']['height'])
|
s['video']['height'])
|
||||||
else:
|
else:
|
||||||
with ts.ignoreStdout():
|
with ts.ignoreStdout(): import cv2
|
||||||
import cv2
|
|
||||||
if s['video']['device'] == 0:
|
if s['video']['device'] == 0:
|
||||||
# webcam
|
# webcam
|
||||||
self._video = cv2.VideoCapture(s['video']['device'], cv2.DSHOW)
|
self._video = cv2.VideoCapture(s['video']['device'], cv2.DSHOW)
|
||||||
@ -327,14 +369,16 @@ class AV(common.tox_save.ToxAvSave):
|
|||||||
+f" supported: {s['video']['width']} {s['video']['height']}")
|
+f" supported: {s['video']['width']} {s['video']['height']}")
|
||||||
|
|
||||||
self._video_running = True
|
self._video_running = True
|
||||||
self._video_thread = BaseThread(target=self.send_video,
|
self._video_thread = VideoThread(self,
|
||||||
name='_video_thread')
|
name='_video_thread')
|
||||||
self._video_thread.start()
|
self._video_thread.start()
|
||||||
|
|
||||||
def stop_video_thread(self):
|
def stop_video_thread(self) -> None:
|
||||||
|
LOG_DEBUG(f"stop_video_thread {self._video_thread}")
|
||||||
if self._video_thread is None:
|
if self._video_thread is None:
|
||||||
return
|
return
|
||||||
|
|
||||||
|
self._video_thread._stop_thread = True
|
||||||
self._video_running = False
|
self._video_running = False
|
||||||
i = 0
|
i = 0
|
||||||
while i < ts.iTHREAD_JOINS:
|
while i < ts.iTHREAD_JOINS:
|
||||||
@ -342,32 +386,37 @@ class AV(common.tox_save.ToxAvSave):
|
|||||||
try:
|
try:
|
||||||
if not self._video_thread.is_alive(): break
|
if not self._video_thread.is_alive(): break
|
||||||
except:
|
except:
|
||||||
# AttributeError: 'NoneType' object has no attribute 'join'
|
|
||||||
break
|
break
|
||||||
i = i + 1
|
i = i + 1
|
||||||
else:
|
else:
|
||||||
LOG.warn("self._video_thread.is_alive BLOCKED")
|
LOG.warn("self._video_thread.is_alive BLOCKED")
|
||||||
self._video_thread = None
|
self._video_thread = None
|
||||||
self._video = None
|
self._video = None
|
||||||
|
|
||||||
# -----------------------------------------------------------------------------------------------------------------
|
|
||||||
# Incoming chunks
|
# Incoming chunks
|
||||||
# -----------------------------------------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
def audio_chunk(self, samples, channels_count, rate):
|
def audio_chunk(self, samples, channels_count, rate) -> None:
|
||||||
"""
|
"""
|
||||||
Incoming chunk
|
Incoming chunk
|
||||||
"""
|
"""
|
||||||
|
# from callback
|
||||||
if self._out_stream is None:
|
if self._out_stream is None:
|
||||||
# was iOutput = self._settings._args.audio['output']
|
# was iOutput = self._settings._args.audio['output']
|
||||||
iOutput = self._settings['audio']['output']
|
iOutput = self._settings['audio']['output']
|
||||||
if not rate in self.lPaSampleratesO:
|
if self.lPaSampleratesO and rate in self.lPaSampleratesO:
|
||||||
LOG.warn(f"{rate} not in {self.lPaSampleratesO!r}")
|
LOG_DEBUG(f"Using rate {rate} in self.lPaSampleratesO")
|
||||||
if False:
|
elif self.lPaSampleratesO:
|
||||||
rate = oPYA.get_device_info_by_index(iOutput)['defaultSampleRate']
|
LOG_WARN(f"{rate} not in {self.lPaSampleratesO}")
|
||||||
LOG.warn(f"Setting audio_rate to: {self.lPaSampleratesO[0]}")
|
LOG_WARN(f"Setting audio_rate to: {self.lPaSampleratesO[0]}")
|
||||||
rate = self.lPaSampleratesO[0]
|
rate = self.lPaSampleratesO[0]
|
||||||
|
elif 'defaultSampleRate' in oPYA.get_device_info_by_index(iOutput):
|
||||||
|
rate = round(oPYA.get_device_info_by_index(iOutput)['defaultSampleRate'])
|
||||||
|
LOG_WARN(f"Setting rate to {rate} empty self.lPaSampleratesO")
|
||||||
|
else:
|
||||||
|
LOG_WARN(f"Using rate {rate} empty self.lPaSampleratesO")
|
||||||
|
if type(rate) == float:
|
||||||
|
rate = round(rate)
|
||||||
|
# test output device?
|
||||||
|
# [Errno -9985] Device unavailable
|
||||||
try:
|
try:
|
||||||
with ts.ignoreStderr():
|
with ts.ignoreStderr():
|
||||||
self._out_stream = oPYA.open(format=pyaudio.paInt16,
|
self._out_stream = oPYA.open(format=pyaudio.paInt16,
|
||||||
@ -376,56 +425,61 @@ class AV(common.tox_save.ToxAvSave):
|
|||||||
output_device_index=iOutput,
|
output_device_index=iOutput,
|
||||||
output=True)
|
output=True)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
LOG.error(f"Error playing audio_chunk creating self._out_stream {e}")
|
LOG_ERROR(f"Error playing audio_chunk creating self._out_stream output_device_index={iOutput} {e}")
|
||||||
invoke_in_main_thread(util_ui.message_box,
|
invoke_in_main_thread(util_ui.message_box,
|
||||||
str(e),
|
str(e),
|
||||||
util_ui.tr("Error Chunking audio"))
|
util_ui.tr("Error Chunking audio"))
|
||||||
# dunno
|
# dunno
|
||||||
self.stop()
|
self.stop()
|
||||||
return
|
return
|
||||||
|
|
||||||
iOutput = self._settings['audio']['output']
|
iOutput = self._settings['audio']['output']
|
||||||
LOG.debug(f"audio_chunk output_device_index={iOutput} rate={rate} channels={channels_count}")
|
#trace LOG_DEBUG(f"audio_chunk output_device_index={iOutput} rate={rate} channels={channels_count}")
|
||||||
self._out_stream.write(samples)
|
try:
|
||||||
|
self._out_stream.write(samples)
|
||||||
|
except Exception as e:
|
||||||
|
# OSError: [Errno -9999] Unanticipated host error
|
||||||
|
LOG_WARN(f"audio_chunk output_device_index={iOutput} {e}")
|
||||||
|
|
||||||
# -----------------------------------------------------------------------------------------------------------------
|
|
||||||
# AV sending
|
# AV sending
|
||||||
# -----------------------------------------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
def send_audio_data(self, data, count, *largs, **kwargs):
|
def send_audio_data(self, data, count, *largs, **kwargs) -> None:
|
||||||
|
# callback
|
||||||
pcm = data
|
pcm = data
|
||||||
# :param sampling_rate: Audio sampling rate used in this frame.
|
# :param sampling_rate: Audio sampling rate used in this frame.
|
||||||
if self._toxav is None:
|
try:
|
||||||
raise RuntimeError("_toxav not initialized")
|
if self._toxav is None:
|
||||||
if self._audio_rate_tox not in ts.lToxSamplerates:
|
LOG_ERROR("_toxav not initialized")
|
||||||
LOG.warn(f"ToxAudio sampling rate was {self._audio_rate_tox} changed to {ts.lToxSamplerates[0]}")
|
return
|
||||||
self._audio_rate_tox = ts.lToxSamplerates[0]
|
if self._audio_rate_tox not in ts.lToxSamplerates:
|
||||||
|
LOG_WARN(f"ToxAudio sampling rate was {self._audio_rate_tox} changed to {ts.lToxSamplerates[0]}")
|
||||||
|
self._audio_rate_tox = ts.lToxSamplerates[0]
|
||||||
|
|
||||||
for friend_num in self._calls:
|
for friend_num in self._calls:
|
||||||
if self._calls[friend_num].out_audio:
|
if self._calls[friend_num].out_audio:
|
||||||
try:
|
|
||||||
# app.av.calls ERROR Error send_audio: One of the frame parameters was invalid. E.g. the resolution may be too small or too large, or the audio sampling rate may be unsupported
|
|
||||||
# app.av.calls ERROR Error send_audio audio_send_frame: This client is currently not in a call with the friend.
|
# app.av.calls ERROR Error send_audio audio_send_frame: This client is currently not in a call with the friend.
|
||||||
self._toxav.audio_send_frame(friend_num,
|
self._toxav.audio_send_frame(friend_num,
|
||||||
pcm,
|
pcm,
|
||||||
count,
|
count,
|
||||||
self._audio_channels,
|
self._audio_channels,
|
||||||
self._audio_rate_tox)
|
self._audio_rate_tox)
|
||||||
except Exception as e:
|
|
||||||
LOG.error(f"Error send_audio audio_send_frame: {e}")
|
|
||||||
LOG.debug(f"send_audio self._audio_rate_tox={self._audio_rate_tox} self._audio_channels={self._audio_channels}")
|
|
||||||
# invoke_in_main_thread(util_ui.message_box,
|
|
||||||
# str(e),
|
|
||||||
# util_ui.tr("Error send_audio audio_send_frame"))
|
|
||||||
pass
|
|
||||||
|
|
||||||
def send_audio(self):
|
except Exception as e:
|
||||||
|
LOG.error(f"Error send_audio_data audio_send_frame: {e}")
|
||||||
|
LOG.debug(f"send_audio_data self._audio_rate_tox={self._audio_rate_tox} self._audio_channels={self._audio_channels}")
|
||||||
|
self.stop_audio_thread()
|
||||||
|
invoke_in_main_thread(util_ui.message_box,
|
||||||
|
str(e),
|
||||||
|
util_ui.tr("Error send_audio_data audio_send_frame"))
|
||||||
|
#? stop ? endcall?
|
||||||
|
|
||||||
|
def send_audio(self) -> None:
|
||||||
"""
|
"""
|
||||||
This method sends audio to friends
|
This method sends audio to friends
|
||||||
"""
|
"""
|
||||||
i=0
|
i=0
|
||||||
count = self._audio_sample_count_tox
|
count = self._audio_sample_count_tox
|
||||||
LOG.debug(f"send_audio stream={self._audio_stream}")
|
LOG_DEBUG(f"send_audio stream={self._audio_stream}")
|
||||||
while self._audio_running:
|
while self._audio_running:
|
||||||
try:
|
try:
|
||||||
pcm = self._audio_stream.read(count, exception_on_overflow=False)
|
pcm = self._audio_stream.read(count, exception_on_overflow=False)
|
||||||
@ -440,48 +494,48 @@ class AV(common.tox_save.ToxAvSave):
|
|||||||
i += 1
|
i += 1
|
||||||
sleep(0.01)
|
sleep(0.01)
|
||||||
|
|
||||||
def send_video(self):
|
def send_video(self) -> None:
|
||||||
"""
|
"""
|
||||||
This method sends video to friends
|
This method sends video to friends
|
||||||
"""
|
"""
|
||||||
LOG.debug(f"send_video thread={threading.current_thread().name}"
|
# LOG_DEBUG(f"send_video thread={threading.current_thread().name}"
|
||||||
+f" self._video_running={self._video_running}"
|
# +f" self._video_running={self._video_running}"
|
||||||
+f" device: {self._settings['video']['device']}" )
|
# +f" device: {self._settings['video']['device']}" )
|
||||||
while self._video_running:
|
while self._video_running:
|
||||||
try:
|
try:
|
||||||
result, frame = self._video.read()
|
result, frame = self._video.read()
|
||||||
if not result:
|
if not result:
|
||||||
LOG.warn(f"send_video video_send_frame _video.read result={result}")
|
LOG_WARN(f"send_video video_send_frame _video.read result={result}")
|
||||||
break
|
break
|
||||||
if frame is None:
|
if frame is None:
|
||||||
LOG.warn(f"send_video video_send_frame _video.read result={result} frame={frame}")
|
LOG_WARN(f"send_video video_send_frame _video.read result={result} frame={frame}")
|
||||||
continue
|
continue
|
||||||
|
|
||||||
|
LOG_TRACE(f"send_video video_send_frame _video.read result={result}")
|
||||||
|
height, width, channels = frame.shape
|
||||||
|
friends = []
|
||||||
|
for friend_num in self._calls:
|
||||||
|
if self._calls[friend_num].out_video:
|
||||||
|
friends.append(friend_num)
|
||||||
|
if len(friends) == 0:
|
||||||
|
LOG_WARN(f"send_video video_send_frame no friends")
|
||||||
else:
|
else:
|
||||||
LOG_TRACE(f"send_video video_send_frame _video.read result={result}")
|
LOG_TRACE(f"send_video video_send_frame {friends}")
|
||||||
height, width, channels = frame.shape
|
friend_num = friends[0]
|
||||||
friends = []
|
try:
|
||||||
for friend_num in self._calls:
|
y, u, v = self.convert_bgr_to_yuv(frame)
|
||||||
if self._calls[friend_num].out_video:
|
self._toxav.video_send_frame(friend_num, width, height, y, u, v)
|
||||||
friends.append(friend_num)
|
except Exception as e:
|
||||||
if len(friends) == 0:
|
LOG_WARN(f"send_video video_send_frame ERROR {e}")
|
||||||
LOG.warn(f"send_video video_send_frame no friends")
|
pass
|
||||||
else:
|
|
||||||
LOG_TRACE(f"send_video video_send_frame {friends}")
|
|
||||||
friend_num = friends[0]
|
|
||||||
try:
|
|
||||||
y, u, v = self.convert_bgr_to_yuv(frame)
|
|
||||||
self._toxav.video_send_frame(friend_num, width, height, y, u, v)
|
|
||||||
except Exception as e:
|
|
||||||
LOG.debug(f"send_video video_send_frame ERROR {e}")
|
|
||||||
pass
|
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
LOG.error(f"send_video video_send_frame {e}")
|
LOG_ERROR(f"send_video video_send_frame {e}")
|
||||||
pass
|
pass
|
||||||
|
|
||||||
sleep( 1.0/iFPS)
|
sleep( 1.0/iFPS)
|
||||||
|
|
||||||
def convert_bgr_to_yuv(self, frame):
|
def convert_bgr_to_yuv(self, frame) -> tuple:
|
||||||
"""
|
"""
|
||||||
:param frame: input bgr frame
|
:param frame: input bgr frame
|
||||||
:return y, u, v: y, u, v values of frame
|
:return y, u, v: y, u, v values of frame
|
||||||
@ -520,11 +574,12 @@ class AV(common.tox_save.ToxAvSave):
|
|||||||
y = list(itertools.chain.from_iterable(y))
|
y = list(itertools.chain.from_iterable(y))
|
||||||
|
|
||||||
import numpy as np
|
import numpy as np
|
||||||
u = np.zeros((self._video_height // 2, self._video_width // 2), dtype=np.int)
|
# was np.int
|
||||||
|
u = np.zeros((self._video_height // 2, self._video_width // 2), dtype=np.int32)
|
||||||
u[::2, :] = frame[self._video_height:self._video_height * 5 // 4, :self._video_width // 2]
|
u[::2, :] = frame[self._video_height:self._video_height * 5 // 4, :self._video_width // 2]
|
||||||
u[1::2, :] = frame[self._video_height:self._video_height * 5 // 4, self._video_width // 2:]
|
u[1::2, :] = frame[self._video_height:self._video_height * 5 // 4, self._video_width // 2:]
|
||||||
u = list(itertools.chain.from_iterable(u))
|
u = list(itertools.chain.from_iterable(u))
|
||||||
v = np.zeros((self._video_height // 2, self._video_width // 2), dtype=np.int)
|
v = np.zeros((self._video_height // 2, self._video_width // 2), dtype=np.int32)
|
||||||
v[::2, :] = frame[self._video_height * 5 // 4:, :self._video_width // 2]
|
v[::2, :] = frame[self._video_height * 5 // 4:, :self._video_width // 2]
|
||||||
v[1::2, :] = frame[self._video_height * 5 // 4:, self._video_width // 2:]
|
v[1::2, :] = frame[self._video_height * 5 // 4:, self._video_width // 2:]
|
||||||
v = list(itertools.chain.from_iterable(v))
|
v = list(itertools.chain.from_iterable(v))
|
||||||
|
@ -2,15 +2,19 @@
|
|||||||
|
|
||||||
import sys
|
import sys
|
||||||
import threading
|
import threading
|
||||||
|
import traceback
|
||||||
|
import logging
|
||||||
|
|
||||||
|
from qtpy import QtCore
|
||||||
|
|
||||||
import av.calls
|
import av.calls
|
||||||
from messenger.messages import *
|
from messenger.messages import *
|
||||||
from ui import av_widgets
|
from ui import av_widgets
|
||||||
import common.event as event
|
import common.event as event
|
||||||
import utils.ui as util_ui
|
import utils.ui as util_ui
|
||||||
|
from toxygen_wrapper.tests import support_testing as ts
|
||||||
|
|
||||||
global LOG
|
global LOG
|
||||||
import logging
|
|
||||||
LOG = logging.getLogger('app.'+__name__)
|
LOG = logging.getLogger('app.'+__name__)
|
||||||
|
|
||||||
class CallsManager:
|
class CallsManager:
|
||||||
@ -27,12 +31,10 @@ class CallsManager:
|
|||||||
self._call_finished_event = event.Event() # friend_number, is_declined
|
self._call_finished_event = event.Event() # friend_number, is_declined
|
||||||
self._app = app
|
self._app = app
|
||||||
|
|
||||||
def set_toxav(self, toxav):
|
def set_toxav(self, toxav) -> None:
|
||||||
self._callav.set_toxav(toxav)
|
self._callav.set_toxav(toxav)
|
||||||
|
|
||||||
# -----------------------------------------------------------------------------------------------------------------
|
|
||||||
# Events
|
# Events
|
||||||
# -----------------------------------------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
def get_call_started_event(self):
|
def get_call_started_event(self):
|
||||||
return self._call_started_event
|
return self._call_started_event
|
||||||
@ -44,11 +46,9 @@ class CallsManager:
|
|||||||
|
|
||||||
call_finished_event = property(get_call_finished_event)
|
call_finished_event = property(get_call_finished_event)
|
||||||
|
|
||||||
# -----------------------------------------------------------------------------------------------------------------
|
|
||||||
# AV support
|
# AV support
|
||||||
# -----------------------------------------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
def call_click(self, audio=True, video=False):
|
def call_click(self, audio=True, video=False) -> None:
|
||||||
"""User clicked audio button in main window"""
|
"""User clicked audio button in main window"""
|
||||||
num = self._contacts_manager.get_active_number()
|
num = self._contacts_manager.get_active_number()
|
||||||
if not self._contacts_manager.is_active_a_friend():
|
if not self._contacts_manager.is_active_a_friend():
|
||||||
@ -62,11 +62,11 @@ class CallsManager:
|
|||||||
elif num in self._callav: # finish or cancel call if you call with active friend
|
elif num in self._callav: # finish or cancel call if you call with active friend
|
||||||
self.stop_call(num, False)
|
self.stop_call(num, False)
|
||||||
|
|
||||||
def incoming_call(self, audio, video, friend_number):
|
def incoming_call(self, audio, video, friend_number) -> None:
|
||||||
"""
|
"""
|
||||||
Incoming call from friend.
|
Incoming call from friend.
|
||||||
"""
|
"""
|
||||||
LOG.debug(__name__ +f" incoming_call {friend_number}")
|
LOG.debug(f"CM incoming_call {friend_number}")
|
||||||
# if not self._settings['audio']['enabled']: return
|
# if not self._settings['audio']['enabled']: return
|
||||||
friend = self._contacts_manager.get_friend_by_number(friend_number)
|
friend = self._contacts_manager.get_friend_by_number(friend_number)
|
||||||
self._call_started_event(friend_number, audio, video, False)
|
self._call_started_event(friend_number, audio, video, False)
|
||||||
@ -80,19 +80,27 @@ class CallsManager:
|
|||||||
self._call_widgets[friend_number].set_pixmap(friend.get_pixmap())
|
self._call_widgets[friend_number].set_pixmap(friend.get_pixmap())
|
||||||
self._call_widgets[friend_number].show()
|
self._call_widgets[friend_number].show()
|
||||||
|
|
||||||
def accept_call(self, friend_number, audio, video):
|
def accept_call(self, friend_number, audio, video) -> None:
|
||||||
"""
|
"""
|
||||||
Accept incoming call with audio or video
|
Accept incoming call with audio or video
|
||||||
Called from a thread
|
Called from a thread
|
||||||
"""
|
"""
|
||||||
|
|
||||||
LOG.debug(f"CM accept_call from {friend_number} {audio} {video}")
|
LOG.debug(f"CM accept_call from friend_number={friend_number} {audio} {video}")
|
||||||
sys.stdout.flush()
|
sys.stdout.flush()
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
self._main_screen.active_call()
|
||||||
|
# failsafe added somewhere this was being left up
|
||||||
|
self.close_call(friend_number)
|
||||||
|
QtCore.QCoreApplication.processEvents()
|
||||||
|
|
||||||
self._callav.call_accept_call(friend_number, audio, video)
|
self._callav.call_accept_call(friend_number, audio, video)
|
||||||
|
LOG.debug(f"accept_call _call.accept_call CALLED f={friend_number}")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
#
|
||||||
LOG.error(f"accept_call _call.accept_call ERROR for {friend_number} {e}")
|
LOG.error(f"accept_call _call.accept_call ERROR for {friend_number} {e}")
|
||||||
|
LOG.debug(traceback.print_exc())
|
||||||
self._main_screen.call_finished()
|
self._main_screen.call_finished()
|
||||||
if hasattr(self._main_screen, '_settings') and \
|
if hasattr(self._main_screen, '_settings') and \
|
||||||
'audio' in self._main_screen._settings and \
|
'audio' in self._main_screen._settings and \
|
||||||
@ -104,64 +112,73 @@ class CallsManager:
|
|||||||
elif hasattr(self._main_screen, '_settings') and \
|
elif hasattr(self._main_screen, '_settings') and \
|
||||||
hasattr(self._main_screen._settings, 'audio') and \
|
hasattr(self._main_screen._settings, 'audio') and \
|
||||||
'input' not in self._main_screen._settings['audio']:
|
'input' not in self._main_screen._settings['audio']:
|
||||||
LOG.warn(f"'audio' not in {self._main_screen._settings!r}")
|
LOG.warn(f"'audio' not in {self._main_screen._settings}")
|
||||||
elif hasattr(self._main_screen, '_settings') and \
|
elif hasattr(self._main_screen, '_settings') and \
|
||||||
hasattr(self._main_screen._settings, 'audio') and \
|
hasattr(self._main_screen._settings, 'audio') and \
|
||||||
'input' not in self._main_screen._settings['audio']:
|
'input' not in self._main_screen._settings['audio']:
|
||||||
LOG.warn(f"'audio' not in {self._main_screen._settings!r}")
|
LOG.warn(f"'audio' not in {self._main_screen._settings}")
|
||||||
else:
|
else:
|
||||||
LOG.warn(f"_settings not in self._main_screen")
|
LOG.warn(f"_settings not in self._main_screen")
|
||||||
util_ui.message_box(str(e),
|
util_ui.message_box(str(e),
|
||||||
util_ui.tr('ERROR Accepting call from {friend_number}'))
|
util_ui.tr('ERROR Accepting call from {friend_number}'))
|
||||||
else:
|
|
||||||
self._main_screen.active_call()
|
|
||||||
|
|
||||||
finally:
|
finally:
|
||||||
# does not terminate call - just the av_widget
|
# does not terminate call - just the av_widget
|
||||||
if friend_number in self._incoming_calls:
|
LOG.debug(f"CM.accept_call close av_widget")
|
||||||
self._incoming_calls.remove(friend_number)
|
self.close_call(friend_number)
|
||||||
try:
|
|
||||||
self._call_widgets[friend_number].close()
|
|
||||||
del self._call_widgets[friend_number]
|
|
||||||
except:
|
|
||||||
# RuntimeError: wrapped C/C++ object of type IncomingCallWidget has been deleted
|
|
||||||
|
|
||||||
pass
|
|
||||||
LOG.debug(f" closed self._call_widgets[{friend_number}]")
|
LOG.debug(f" closed self._call_widgets[{friend_number}]")
|
||||||
|
|
||||||
def stop_call(self, friend_number, by_friend):
|
def close_call(self, friend_number:int) -> None:
|
||||||
|
# refactored out from above because the accept window not getting
|
||||||
|
# taken down in some accept audio calls
|
||||||
|
LOG.debug(f"close_call {friend_number}")
|
||||||
|
try:
|
||||||
|
if friend_number in self._call_widgets:
|
||||||
|
self._call_widgets[friend_number].close()
|
||||||
|
del self._call_widgets[friend_number]
|
||||||
|
if friend_number in self._incoming_calls:
|
||||||
|
self._incoming_calls.remove(friend_number)
|
||||||
|
except Exception as e:
|
||||||
|
# RuntimeError: wrapped C/C++ object of type IncomingCallWidget has been deleted
|
||||||
|
|
||||||
|
LOG.warn(f" closed self._call_widgets[{friend_number}] {e}")
|
||||||
|
# invoke_in_main_thread(QtCore.QCoreApplication.processEvents)
|
||||||
|
QtCore.QCoreApplication.processEvents()
|
||||||
|
|
||||||
|
|
||||||
|
def stop_call(self, friend_number, by_friend) -> None:
|
||||||
"""
|
"""
|
||||||
Stop call with friend
|
Stop call with friend
|
||||||
"""
|
"""
|
||||||
LOG.debug(__name__+f" stop_call {friend_number}")
|
LOG.debug(f"CM.stop_call friend={friend_number}")
|
||||||
if friend_number in self._incoming_calls:
|
if friend_number in self._incoming_calls:
|
||||||
self._incoming_calls.remove(friend_number)
|
self._incoming_calls.remove(friend_number)
|
||||||
is_declined = True
|
is_declined = True
|
||||||
else:
|
else:
|
||||||
is_declined = False
|
is_declined = False
|
||||||
|
if friend_number in self._call_widgets:
|
||||||
|
LOG.debug(f"CM.stop_call _call_widgets close")
|
||||||
|
self.close_call(friend_number)
|
||||||
|
|
||||||
|
LOG.debug(f"CM.stop_call _main_screen.call_finished")
|
||||||
self._main_screen.call_finished()
|
self._main_screen.call_finished()
|
||||||
self._callav.finish_call(friend_number, by_friend) # finish or decline call
|
self._callav.finish_call(friend_number, by_friend) # finish or decline call
|
||||||
if friend_number in self._call_widgets:
|
is_video = self._callav.is_video_call(friend_number)
|
||||||
self._call_widgets[friend_number].close()
|
if is_video:
|
||||||
del self._call_widgets[friend_number]
|
def destroy_window():
|
||||||
|
#??? FixMe
|
||||||
def destroy_window():
|
with ts.ignoreStdout(): import cv2
|
||||||
#??? FixMed
|
|
||||||
is_video = self._callav.is_video_call(friend_number)
|
|
||||||
if is_video:
|
|
||||||
import cv2
|
|
||||||
cv2.destroyWindow(str(friend_number))
|
cv2.destroyWindow(str(friend_number))
|
||||||
|
LOG.debug(f"CM.stop_call destroy_window")
|
||||||
|
threading.Timer(2.0, destroy_window).start()
|
||||||
|
|
||||||
threading.Timer(2.0, destroy_window).start()
|
LOG.debug(f"CM.stop_call _call_finished_event")
|
||||||
self._call_finished_event(friend_number, is_declined)
|
self._call_finished_event(friend_number, is_declined)
|
||||||
|
|
||||||
def friend_exit(self, friend_number):
|
def friend_exit(self, friend_number:int) -> None:
|
||||||
if friend_number in self._callav:
|
if friend_number in self._callav:
|
||||||
self._callav.finish_call(friend_number, True)
|
self._callav.finish_call(friend_number, True)
|
||||||
|
|
||||||
# -----------------------------------------------------------------------------------------------------------------
|
|
||||||
# Private methods
|
# Private methods
|
||||||
# -----------------------------------------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
def _get_incoming_call_widget(self, friend_number, text, friend_name):
|
def _get_incoming_call_widget(self, friend_number, text, friend_name):
|
||||||
return av_widgets.IncomingCallWidget(self._settings, self, friend_number, text, friend_name)
|
return av_widgets.IncomingCallWidget(self._settings, self, friend_number, text, friend_name)
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
from PyQt5 import QtWidgets
|
# -*- mode: python; indent-tabs-mode: nil; py-indent-offset: 4; coding: utf-8 -*-
|
||||||
|
|
||||||
|
from qtpy import QtWidgets
|
||||||
|
|
||||||
class DesktopGrabber:
|
class DesktopGrabber:
|
||||||
|
|
||||||
@ -12,7 +13,7 @@ class DesktopGrabber:
|
|||||||
self._height -= height % 4
|
self._height -= height % 4
|
||||||
self._screen = QtWidgets.QApplication.primaryScreen()
|
self._screen = QtWidgets.QApplication.primaryScreen()
|
||||||
|
|
||||||
def read(self):
|
def read(self) -> tuple:
|
||||||
pixmap = self._screen.grabWindow(0, self._x, self._y, self._width, self._height)
|
pixmap = self._screen.grabWindow(0, self._x, self._y, self._width, self._height)
|
||||||
image = pixmap.toImage()
|
image = pixmap.toImage()
|
||||||
s = image.bits().asstring(self._width * self._height * 4)
|
s = image.bits().asstring(self._width * self._height * 4)
|
||||||
|
@ -1,9 +1,8 @@
|
|||||||
# -*- mode: python; indent-tabs-mode: nil; py-indent-offset: 4; coding: utf-8 -*-
|
# -*- mode: python; indent-tabs-mode: nil; py-indent-offset: 4; coding: utf-8 -*-
|
||||||
import random
|
import random
|
||||||
import urllib.request
|
import logging
|
||||||
from utils.util import *
|
|
||||||
from PyQt5 import QtNetwork
|
from qtpy import QtCore
|
||||||
from PyQt5 import QtCore
|
|
||||||
try:
|
try:
|
||||||
import certifi
|
import certifi
|
||||||
from io import BytesIO
|
from io import BytesIO
|
||||||
@ -11,15 +10,16 @@ except ImportError:
|
|||||||
certifi = None
|
certifi = None
|
||||||
|
|
||||||
from user_data.settings import get_user_config_path
|
from user_data.settings import get_user_config_path
|
||||||
from wrapper_tests.support_testing import _get_nodes_path
|
from utils.util import *
|
||||||
from wrapper_tests.support_http import download_url
|
|
||||||
import wrapper_tests.support_testing as ts
|
from toxygen_wrapper.tests.support_testing import _get_nodes_path
|
||||||
|
from toxygen_wrapper.tests.support_http import download_url
|
||||||
|
import toxygen_wrapper.tests.support_testing as ts
|
||||||
|
|
||||||
global LOG
|
global LOG
|
||||||
import logging
|
|
||||||
LOG = logging.getLogger('app.'+'bootstrap')
|
LOG = logging.getLogger('app.'+'bootstrap')
|
||||||
|
|
||||||
def download_nodes_list(settings, oArgs):
|
def download_nodes_list(settings, oArgs) -> str:
|
||||||
if not settings['download_nodes_list']:
|
if not settings['download_nodes_list']:
|
||||||
return ''
|
return ''
|
||||||
if not ts.bAreWeConnected():
|
if not ts.bAreWeConnected():
|
||||||
@ -40,9 +40,9 @@ def download_nodes_list(settings, oArgs):
|
|||||||
_save_nodes(result, settings._app)
|
_save_nodes(result, settings._app)
|
||||||
return result
|
return result
|
||||||
|
|
||||||
def _save_nodes(nodes, app):
|
def _save_nodes(nodes, app) -> None:
|
||||||
if not nodes:
|
if not nodes:
|
||||||
return
|
return
|
||||||
with open(_get_nodes_path(oArgs=app._args), 'wb') as fl:
|
with open(_get_nodes_path(app._args), 'wb') as fl:
|
||||||
LOG.info("Saving nodes to " +_get_nodes_path())
|
LOG.info("Saving nodes to " +_get_nodes_path(app._args))
|
||||||
fl.write(nodes)
|
fl.write(nodes)
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
|
# -*- mode: python; indent-tabs-mode: nil; py-indent-offset: 4; coding: utf-8 -*-
|
||||||
|
|
||||||
class Event:
|
class Event:
|
||||||
|
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
|
# -*- mode: python; indent-tabs-mode: nil; py-indent-offset: 4; coding: utf-8 -*-
|
||||||
|
|
||||||
class Provider:
|
class Provider:
|
||||||
|
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
|
# -*- mode: python; indent-tabs-mode: nil; py-indent-offset: 4; coding: utf-8 -*-
|
||||||
|
|
||||||
class ToxSave:
|
class ToxSave:
|
||||||
|
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
# -*- mode: python; indent-tabs-mode: nil; py-indent-offset: 4; coding: utf-8 -*-
|
# -*- mode: python; indent-tabs-mode: nil; py-indent-offset: 4; coding: utf-8 -*-
|
||||||
from user_data.settings import *
|
from user_data.settings import *
|
||||||
from PyQt5 import QtCore, QtGui
|
from qtpy import QtCore, QtGui
|
||||||
from wrapper.toxcore_enums_and_consts import TOX_PUBLIC_KEY_SIZE
|
from toxygen_wrapper.toxcore_enums_and_consts import TOX_PUBLIC_KEY_SIZE
|
||||||
import utils.util as util
|
import utils.util as util
|
||||||
import common.event as event
|
import common.event as event
|
||||||
import contacts.common as common
|
import contacts.common as common
|
||||||
@ -35,9 +35,7 @@ class BaseContact:
|
|||||||
self._avatar_changed_event = event.Event()
|
self._avatar_changed_event = event.Event()
|
||||||
self.init_widget()
|
self.init_widget()
|
||||||
|
|
||||||
# -----------------------------------------------------------------------------------------------------------------
|
|
||||||
# Name - current name or alias of user
|
# Name - current name or alias of user
|
||||||
# -----------------------------------------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
def get_name(self):
|
def get_name(self):
|
||||||
return self._name
|
return self._name
|
||||||
@ -57,9 +55,7 @@ class BaseContact:
|
|||||||
|
|
||||||
name_changed_event = property(get_name_changed_event)
|
name_changed_event = property(get_name_changed_event)
|
||||||
|
|
||||||
# -----------------------------------------------------------------------------------------------------------------
|
|
||||||
# Status message
|
# Status message
|
||||||
# -----------------------------------------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
def get_status_message(self):
|
def get_status_message(self):
|
||||||
return self._status_message
|
return self._status_message
|
||||||
@ -79,9 +75,7 @@ class BaseContact:
|
|||||||
|
|
||||||
status_message_changed_event = property(get_status_message_changed_event)
|
status_message_changed_event = property(get_status_message_changed_event)
|
||||||
|
|
||||||
# -----------------------------------------------------------------------------------------------------------------
|
|
||||||
# Status
|
# Status
|
||||||
# -----------------------------------------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
def get_status(self):
|
def get_status(self):
|
||||||
return self._status
|
return self._status
|
||||||
@ -100,30 +94,29 @@ class BaseContact:
|
|||||||
|
|
||||||
status_changed_event = property(get_status_changed_event)
|
status_changed_event = property(get_status_changed_event)
|
||||||
|
|
||||||
# -----------------------------------------------------------------------------------------------------------------
|
|
||||||
# TOX ID. WARNING: for friend it will return public key, for profile - full address
|
# TOX ID. WARNING: for friend it will return public key, for profile - full address
|
||||||
# -----------------------------------------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
def get_tox_id(self):
|
def get_tox_id(self):
|
||||||
return self._tox_id
|
return self._tox_id
|
||||||
|
|
||||||
tox_id = property(get_tox_id)
|
tox_id = property(get_tox_id)
|
||||||
|
|
||||||
# -----------------------------------------------------------------------------------------------------------------
|
|
||||||
# Avatars
|
# Avatars
|
||||||
# -----------------------------------------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
def load_avatar(self):
|
def load_avatar(self):
|
||||||
"""
|
"""
|
||||||
Tries to load avatar of contact or uses default avatar
|
Tries to load avatar of contact or uses default avatar
|
||||||
"""
|
"""
|
||||||
avatar_path = self.get_avatar_path()
|
try:
|
||||||
width = self._widget.avatar_label.width()
|
avatar_path = self.get_avatar_path()
|
||||||
pixmap = QtGui.QPixmap(avatar_path)
|
width = self._widget.avatar_label.width()
|
||||||
self._widget.avatar_label.setPixmap(pixmap.scaled(width, width, QtCore.Qt.KeepAspectRatio,
|
pixmap = QtGui.QPixmap(avatar_path)
|
||||||
QtCore.Qt.SmoothTransformation))
|
self._widget.avatar_label.setPixmap(pixmap.scaled(width, width, QtCore.Qt.KeepAspectRatio,
|
||||||
self._widget.avatar_label.repaint()
|
QtCore.Qt.SmoothTransformation))
|
||||||
self._avatar_changed_event(avatar_path)
|
self._widget.avatar_label.repaint()
|
||||||
|
self._avatar_changed_event(avatar_path)
|
||||||
|
except Exception as e:
|
||||||
|
pass
|
||||||
|
|
||||||
def reset_avatar(self, generate_new):
|
def reset_avatar(self, generate_new):
|
||||||
avatar_path = self.get_avatar_path()
|
avatar_path = self.get_avatar_path()
|
||||||
@ -165,11 +158,15 @@ class BaseContact:
|
|||||||
|
|
||||||
avatar_changed_event = property(get_avatar_changed_event)
|
avatar_changed_event = property(get_avatar_changed_event)
|
||||||
|
|
||||||
# -----------------------------------------------------------------------------------------------------------------
|
|
||||||
# Widgets
|
# Widgets
|
||||||
# -----------------------------------------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
def init_widget(self):
|
def init_widget(self):
|
||||||
|
# File "/mnt/o/var/local/src/toxygen/toxygen/contacts/contacts_manager.py", line 252, in filtration_and_sorting
|
||||||
|
# contact.set_widget(item_widget)
|
||||||
|
# File "/mnt/o/var/local/src/toxygen/toxygen/contacts/contact.py", line 320, in set_widget
|
||||||
|
if not self._widget:
|
||||||
|
LOG.warn("BC.init_widget self._widget is NULL")
|
||||||
|
return
|
||||||
self._widget.name.setText(self._name)
|
self._widget.name.setText(self._name)
|
||||||
self._widget.status_message.setText(self._status_message)
|
self._widget.status_message.setText(self._status_message)
|
||||||
if hasattr(self._widget, 'kind'):
|
if hasattr(self._widget, 'kind'):
|
||||||
@ -177,9 +174,7 @@ class BaseContact:
|
|||||||
self._widget.connection_status.update(self._status)
|
self._widget.connection_status.update(self._status)
|
||||||
self.load_avatar()
|
self.load_avatar()
|
||||||
|
|
||||||
# -----------------------------------------------------------------------------------------------------------------
|
|
||||||
# Private methods
|
# Private methods
|
||||||
# -----------------------------------------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _get_default_avatar_path():
|
def _get_default_avatar_path():
|
||||||
|
@ -1,10 +1,10 @@
|
|||||||
|
# -*- mode: python; indent-tabs-mode: nil; py-indent-offset: 4; coding: utf-8 -*-
|
||||||
|
|
||||||
import hashlib
|
import hashlib
|
||||||
|
|
||||||
from pydenticon import Generator
|
from pydenticon import Generator
|
||||||
|
|
||||||
# -----------------------------------------------------------------------------------------------------------------
|
|
||||||
# Typing notifications
|
# Typing notifications
|
||||||
# -----------------------------------------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
class BaseTypingNotificationHandler:
|
class BaseTypingNotificationHandler:
|
||||||
|
|
||||||
@ -19,7 +19,7 @@ class BaseTypingNotificationHandler:
|
|||||||
|
|
||||||
class FriendTypingNotificationHandler(BaseTypingNotificationHandler):
|
class FriendTypingNotificationHandler(BaseTypingNotificationHandler):
|
||||||
|
|
||||||
def __init__(self, friend_number):
|
def __init__(self, friend_number:int):
|
||||||
super().__init__()
|
super().__init__()
|
||||||
self._friend_number = friend_number
|
self._friend_number = friend_number
|
||||||
|
|
||||||
@ -30,9 +30,7 @@ class FriendTypingNotificationHandler(BaseTypingNotificationHandler):
|
|||||||
BaseTypingNotificationHandler.DEFAULT_HANDLER = BaseTypingNotificationHandler()
|
BaseTypingNotificationHandler.DEFAULT_HANDLER = BaseTypingNotificationHandler()
|
||||||
|
|
||||||
|
|
||||||
# -----------------------------------------------------------------------------------------------------------------
|
|
||||||
# Identicons support
|
# Identicons support
|
||||||
# -----------------------------------------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
|
|
||||||
def generate_avatar(public_key):
|
def generate_avatar(public_key):
|
||||||
|
@ -42,9 +42,7 @@ class Contact(basecontact.BaseContact):
|
|||||||
if hasattr(self, '_message_getter'):
|
if hasattr(self, '_message_getter'):
|
||||||
del self._message_getter
|
del self._message_getter
|
||||||
|
|
||||||
# -----------------------------------------------------------------------------------------------------------------
|
|
||||||
# History support
|
# History support
|
||||||
# -----------------------------------------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
def load_corr(self, first_time=True):
|
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)
|
return TextMessage(message, author, unix_time, message_type, unique_id)
|
||||||
|
|
||||||
# -----------------------------------------------------------------------------------------------------------------
|
|
||||||
# Unsent messages
|
# Unsent messages
|
||||||
# -----------------------------------------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
def get_unsent_messages(self):
|
def get_unsent_messages(self):
|
||||||
"""
|
"""
|
||||||
@ -136,24 +132,23 @@ class Contact(basecontact.BaseContact):
|
|||||||
"""
|
"""
|
||||||
:return list of unsent messages for saving
|
:return list of unsent messages for saving
|
||||||
"""
|
"""
|
||||||
messages = filter(lambda m: m.type in (MESSAGE_TYPE['TEXT'], MESSAGE_TYPE['ACTION'])
|
# and m.tox_message_id == tox_message_id,
|
||||||
and m.author.type == MESSAGE_AUTHOR['NOT_SENT'], self._corr)
|
messages = filter(lambda m: m.author is not None
|
||||||
|
and m.author.type == MESSAGE_AUTHOR['NOT_SENT'],
|
||||||
|
self._corr)
|
||||||
|
# was message = list(...)[0]
|
||||||
return list(messages)
|
return list(messages)
|
||||||
|
|
||||||
def mark_as_sent(self, tox_message_id):
|
def mark_as_sent(self, tox_message_id):
|
||||||
message = list(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]
|
|
||||||
try:
|
try:
|
||||||
|
message = list(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]
|
||||||
message.mark_as_sent()
|
message.mark_as_sent()
|
||||||
except Exception as ex:
|
except Exception as ex:
|
||||||
# wrapped C/C++ object of type QLabel has been deleted
|
# wrapped C/C++ object of type QLabel has been deleted
|
||||||
LOG.error(f"Mark as sent: {ex!s}")
|
LOG.error(f"Mark as sent: {ex}")
|
||||||
|
|
||||||
# -----------------------------------------------------------------------------------------------------------------
|
|
||||||
# Message deletion
|
# Message deletion
|
||||||
# -----------------------------------------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
def delete_message(self, message_id):
|
def delete_message(self, message_id):
|
||||||
elem = list(filter(lambda m: m.message_id == message_id, self._corr))[0]
|
elem = list(filter(lambda m: m.message_id == message_id, self._corr))[0]
|
||||||
@ -199,9 +194,7 @@ class Contact(basecontact.BaseContact):
|
|||||||
self._corr))
|
self._corr))
|
||||||
self._unsaved_messages = len(self.get_unsent_messages())
|
self._unsaved_messages = len(self.get_unsent_messages())
|
||||||
|
|
||||||
# -----------------------------------------------------------------------------------------------------------------
|
|
||||||
# Chat history search
|
# Chat history search
|
||||||
# -----------------------------------------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
def search_string(self, search_string):
|
def search_string(self, search_string):
|
||||||
self._search_string, self._search_index = search_string, 0
|
self._search_string, self._search_index = search_string, 0
|
||||||
@ -234,9 +227,7 @@ class Contact(basecontact.BaseContact):
|
|||||||
return i
|
return i
|
||||||
return None # not found
|
return None # not found
|
||||||
|
|
||||||
# -----------------------------------------------------------------------------------------------------------------
|
|
||||||
# Current text - text from message area
|
# Current text - text from message area
|
||||||
# -----------------------------------------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
def get_curr_text(self):
|
def get_curr_text(self):
|
||||||
return self._curr_text
|
return self._curr_text
|
||||||
@ -246,9 +237,7 @@ class Contact(basecontact.BaseContact):
|
|||||||
|
|
||||||
curr_text = property(get_curr_text, set_curr_text)
|
curr_text = property(get_curr_text, set_curr_text)
|
||||||
|
|
||||||
# -----------------------------------------------------------------------------------------------------------------
|
|
||||||
# Alias support
|
# Alias support
|
||||||
# -----------------------------------------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
def set_name(self, value):
|
def set_name(self, value):
|
||||||
"""
|
"""
|
||||||
@ -264,9 +253,7 @@ class Contact(basecontact.BaseContact):
|
|||||||
def has_alias(self):
|
def has_alias(self):
|
||||||
return self._alias
|
return self._alias
|
||||||
|
|
||||||
# -----------------------------------------------------------------------------------------------------------------
|
|
||||||
# Visibility in friends' list
|
# Visibility in friends' list
|
||||||
# -----------------------------------------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
def get_visibility(self):
|
def get_visibility(self):
|
||||||
return self._visible
|
return self._visible
|
||||||
@ -276,9 +263,7 @@ class Contact(basecontact.BaseContact):
|
|||||||
|
|
||||||
visibility = property(get_visibility, set_visibility)
|
visibility = property(get_visibility, set_visibility)
|
||||||
|
|
||||||
# -----------------------------------------------------------------------------------------------------------------
|
|
||||||
# Unread messages and other actions from friend
|
# Unread messages and other actions from friend
|
||||||
# -----------------------------------------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
def get_actions(self):
|
def get_actions(self):
|
||||||
return self._new_actions
|
return self._new_actions
|
||||||
@ -306,9 +291,7 @@ class Contact(basecontact.BaseContact):
|
|||||||
|
|
||||||
messages = property(get_messages)
|
messages = property(get_messages)
|
||||||
|
|
||||||
# -----------------------------------------------------------------------------------------------------------------
|
|
||||||
# Friend's or group's number (can be used in toxcore)
|
# Friend's or group's number (can be used in toxcore)
|
||||||
# -----------------------------------------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
def get_number(self):
|
def get_number(self):
|
||||||
return self._number
|
return self._number
|
||||||
@ -318,25 +301,19 @@ class Contact(basecontact.BaseContact):
|
|||||||
|
|
||||||
number = property(get_number, set_number)
|
number = property(get_number, set_number)
|
||||||
|
|
||||||
# -----------------------------------------------------------------------------------------------------------------
|
|
||||||
# Typing notifications
|
# Typing notifications
|
||||||
# -----------------------------------------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
def get_typing_notification_handler(self):
|
def get_typing_notification_handler(self):
|
||||||
return common.BaseTypingNotificationHandler.DEFAULT_HANDLER
|
return common.BaseTypingNotificationHandler.DEFAULT_HANDLER
|
||||||
|
|
||||||
typing_notification_handler = property(get_typing_notification_handler)
|
typing_notification_handler = property(get_typing_notification_handler)
|
||||||
|
|
||||||
# -----------------------------------------------------------------------------------------------------------------
|
|
||||||
# Context menu support
|
# Context menu support
|
||||||
# -----------------------------------------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
def get_context_menu_generator(self):
|
def get_context_menu_generator(self):
|
||||||
return BaseContactMenuGenerator(self)
|
return BaseContactMenuGenerator(self)
|
||||||
|
|
||||||
# -----------------------------------------------------------------------------------------------------------------
|
|
||||||
# Filtration support
|
# Filtration support
|
||||||
# -----------------------------------------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
def set_widget(self, widget):
|
def set_widget(self, widget):
|
||||||
self._widget = widget
|
self._widget = widget
|
||||||
|
@ -1,16 +1,14 @@
|
|||||||
# -*- mode: python; indent-tabs-mode: nil; py-indent-offset: 4; coding: utf-8 -*-
|
# -*- mode: python; indent-tabs-mode: nil; py-indent-offset: 4; coding: utf-8 -*-
|
||||||
from PyQt5 import QtWidgets
|
from qtpy import QtWidgets
|
||||||
|
|
||||||
import utils.ui as util_ui
|
import utils.ui as util_ui
|
||||||
from wrapper.toxcore_enums_and_consts import *
|
from toxygen_wrapper.toxcore_enums_and_consts import *
|
||||||
|
|
||||||
global LOG
|
global LOG
|
||||||
import logging
|
import logging
|
||||||
LOG = logging.getLogger('app')
|
LOG = logging.getLogger('app')
|
||||||
|
|
||||||
# -----------------------------------------------------------------------------------------------------------------
|
|
||||||
# Builder
|
# Builder
|
||||||
# -----------------------------------------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
def _create_menu(menu_name, parent):
|
def _create_menu(menu_name, parent):
|
||||||
menu_name = menu_name or ''
|
menu_name = menu_name or ''
|
||||||
@ -83,9 +81,7 @@ class ContactMenuBuilder:
|
|||||||
self._actions[self._index] = (text, handler)
|
self._actions[self._index] = (text, handler)
|
||||||
self._index += 1
|
self._index += 1
|
||||||
|
|
||||||
# -----------------------------------------------------------------------------------------------------------------
|
|
||||||
# Generators
|
# Generators
|
||||||
# -----------------------------------------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
|
|
||||||
class BaseContactMenuGenerator:
|
class BaseContactMenuGenerator:
|
||||||
@ -96,9 +92,7 @@ class BaseContactMenuGenerator:
|
|||||||
def generate(self, plugin_loader, contacts_manager, main_screen, settings, number, groups_service, history_loader):
|
def generate(self, plugin_loader, contacts_manager, main_screen, settings, number, groups_service, history_loader):
|
||||||
return ContactMenuBuilder().build()
|
return ContactMenuBuilder().build()
|
||||||
|
|
||||||
# -----------------------------------------------------------------------------------------------------------------
|
|
||||||
# Private methods
|
# Private methods
|
||||||
# -----------------------------------------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
def _generate_copy_menu_builder(self, main_screen):
|
def _generate_copy_menu_builder(self, main_screen):
|
||||||
copy_menu_builder = ContactMenuBuilder()
|
copy_menu_builder = ContactMenuBuilder()
|
||||||
@ -150,9 +144,7 @@ class FriendMenuGenerator(BaseContactMenuGenerator):
|
|||||||
|
|
||||||
return menu
|
return menu
|
||||||
|
|
||||||
# -----------------------------------------------------------------------------------------------------------------
|
|
||||||
# Private methods
|
# Private methods
|
||||||
# -----------------------------------------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _generate_plugins_menu_builder(plugin_loader, number):
|
def _generate_plugins_menu_builder(plugin_loader, number):
|
||||||
|
@ -7,36 +7,25 @@ import logging
|
|||||||
LOG = logging.getLogger(__name__)
|
LOG = logging.getLogger(__name__)
|
||||||
|
|
||||||
# callbacks can be called in any thread so were being careful
|
# callbacks can be called in any thread so were being careful
|
||||||
def LOG_ERROR(l): print('EROR< '+l)
|
from av.calls import LOG_ERROR, LOG_WARN, LOG_INFO, LOG_DEBUG, LOG_TRACE
|
||||||
def LOG_WARN(l): print('WARN< '+l)
|
|
||||||
def LOG_INFO(l):
|
|
||||||
bIsVerbose = hasattr(__builtins__, 'app') and app.oArgs.loglevel <= 20-1
|
|
||||||
if bIsVerbose: print('INFO< '+l)
|
|
||||||
def LOG_DEBUG(l):
|
|
||||||
bIsVerbose = hasattr(__builtins__, 'app') and app.oArgs.loglevel <= 10-1
|
|
||||||
if bIsVerbose: print('DBUG< '+l)
|
|
||||||
def LOG_TRACE(l):
|
|
||||||
bIsVerbose = hasattr(__builtins__, 'app') and app.oArgs.loglevel < 10-1
|
|
||||||
pass # print('TRACE+ '+l)
|
|
||||||
|
|
||||||
class ContactProvider(tox_save.ToxSave):
|
class ContactProvider(tox_save.ToxSave):
|
||||||
|
|
||||||
def __init__(self, tox, friend_factory, group_factory, group_peer_factory):
|
def __init__(self, tox, friend_factory, group_factory, group_peer_factory, app=None):
|
||||||
super().__init__(tox)
|
super().__init__(tox)
|
||||||
self._friend_factory = friend_factory
|
self._friend_factory = friend_factory
|
||||||
self._group_factory = group_factory
|
self._group_factory = group_factory
|
||||||
self._group_peer_factory = group_peer_factory
|
self._group_peer_factory = group_peer_factory
|
||||||
self._cache = {} # key - contact's public key, value - contact instance
|
self._cache = {} # key - contact's public key, value - contact instance
|
||||||
|
self._app = app
|
||||||
|
|
||||||
# -----------------------------------------------------------------------------------------------------------------
|
|
||||||
# Friends
|
# Friends
|
||||||
# -----------------------------------------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
def get_friend_by_number(self, friend_number):
|
def get_friend_by_number(self, friend_number:int):
|
||||||
try:
|
try:
|
||||||
public_key = self._tox.friend_get_public_key(friend_number)
|
public_key = self._tox.friend_get_public_key(friend_number)
|
||||||
except Exception as e:
|
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 None
|
||||||
return self.get_friend_by_public_key(public_key)
|
return self.get_friend_by_public_key(public_key)
|
||||||
|
|
||||||
@ -45,24 +34,25 @@ class ContactProvider(tox_save.ToxSave):
|
|||||||
if friend is not None:
|
if friend is not None:
|
||||||
return friend
|
return friend
|
||||||
friend = self._friend_factory.create_friend_by_public_key(public_key)
|
friend = self._friend_factory.create_friend_by_public_key(public_key)
|
||||||
self._add_to_cache(public_key, friend)
|
if friend is None:
|
||||||
LOG_INFO(f"get_friend_by_public_key ADDED {friend} ")
|
LOG_WARN(f"CP.get_friend_by_public_key NULL {friend} ")
|
||||||
|
else:
|
||||||
|
self._add_to_cache(public_key, friend)
|
||||||
|
LOG_DEBUG(f"CP.get_friend_by_public_key ADDED {friend} ")
|
||||||
return friend
|
return friend
|
||||||
|
|
||||||
def get_all_friends(self):
|
def get_all_friends(self) -> list:
|
||||||
|
if self._app and self._app.bAppExiting:
|
||||||
|
return []
|
||||||
try:
|
try:
|
||||||
friend_numbers = self._tox.self_get_friend_list()
|
friend_numbers = self._tox.self_get_friend_list()
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
LOG_WARN(f"get_all_friends NO {friend_numbers} {e} ")
|
LOG_WARN(f"CP.get_all_friends EXCEPTION {e} ")
|
||||||
return None
|
return []
|
||||||
friends = map(lambda n: self.get_friend_by_number(n), friend_numbers)
|
friends = map(lambda n: self.get_friend_by_number(n), friend_numbers)
|
||||||
|
|
||||||
return list(friends)
|
return list(friends)
|
||||||
|
|
||||||
# -----------------------------------------------------------------------------------------------------------------
|
|
||||||
# Groups
|
# Groups
|
||||||
# -----------------------------------------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
def get_all_groups(self):
|
def get_all_groups(self):
|
||||||
"""from callbacks"""
|
"""from callbacks"""
|
||||||
@ -75,11 +65,11 @@ class ContactProvider(tox_save.ToxSave):
|
|||||||
# failsafe in case there are bogus None groups?
|
# failsafe in case there are bogus None groups?
|
||||||
fgroups = list(filter(lambda x: x, groups))
|
fgroups = list(filter(lambda x: x, groups))
|
||||||
if len(fgroups) != len_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:
|
for group_num in group_numbers:
|
||||||
group = self.get_group_by_number(group_num)
|
group = self.get_group_by_number(group_num)
|
||||||
if group is None:
|
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
|
# fixme: do something
|
||||||
groups = fgroups
|
groups = fgroups
|
||||||
return groups
|
return groups
|
||||||
@ -87,25 +77,24 @@ class ContactProvider(tox_save.ToxSave):
|
|||||||
def get_group_by_number(self, group_number):
|
def get_group_by_number(self, group_number):
|
||||||
group = None
|
group = None
|
||||||
try:
|
try:
|
||||||
LOG_INFO(f"group_get_number {group_number} ")
|
# LOG_DEBUG(f"CP.CP.group_get_number {group_number} ")
|
||||||
# original code
|
# original code
|
||||||
chat_id = self._tox.group_get_chat_id(group_number)
|
chat_id = self._tox.group_get_chat_id(group_number)
|
||||||
if not chat_id:
|
if chat_id is None:
|
||||||
LOG_ERROR(f"get_group_by_number NULL number ({group_number})")
|
LOG_ERROR(f"get_group_by_number NULL chat_id ({group_number})")
|
||||||
|
elif chat_id == '-1':
|
||||||
|
LOG_ERROR(f"get_group_by_number <0 chat_id ({group_number})")
|
||||||
else:
|
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)
|
group = self.get_group_by_chat_id(chat_id)
|
||||||
if not group:
|
if group is None or group == '-1':
|
||||||
LOG_ERROR(f"get_group_by_number NULL group ({chat_id})")
|
LOG_WARN(f"CP.get_group_by_number leaving {group} ({group_number})")
|
||||||
if group is None:
|
#? iRet = self._tox.group_leave(group_number)
|
||||||
|
# invoke in main thread?
|
||||||
LOG_WARN(f"get_group_by_number leaving ({group_number})")
|
# self._contacts_manager.delete_group(group_number)
|
||||||
#? iRet = self._tox.group_leave(group_number)
|
|
||||||
# invoke in main thread?
|
|
||||||
# self._contacts_manager.delete_group(group_number)
|
|
||||||
return group
|
return group
|
||||||
except Exception as e:
|
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
|
return None
|
||||||
|
|
||||||
def get_group_by_chat_id(self, chat_id):
|
def get_group_by_chat_id(self, chat_id):
|
||||||
@ -126,39 +115,37 @@ class ContactProvider(tox_save.ToxSave):
|
|||||||
return group
|
return group
|
||||||
group = self._group_factory.create_group_by_public_key(public_key)
|
group = self._group_factory.create_group_by_public_key(public_key)
|
||||||
if group is None:
|
if group is None:
|
||||||
LOG_ERROR(f"get_group_by_public_key NULL group public_key={get_group_by_chat_id}")
|
LOG_WARN(f"get_group_by_public_key NULL group public_key={public_key}")
|
||||||
else:
|
else:
|
||||||
self._add_to_cache(public_key, group)
|
self._add_to_cache(public_key, group)
|
||||||
|
|
||||||
return group
|
return group
|
||||||
|
|
||||||
# -----------------------------------------------------------------------------------------------------------------
|
|
||||||
# Group peers
|
# Group peers
|
||||||
# -----------------------------------------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
def get_all_group_peers(self):
|
def get_all_group_peers(self):
|
||||||
return list()
|
return []
|
||||||
|
|
||||||
def get_group_peer_by_id(self, group, peer_id):
|
def get_group_peer_by_id(self, group, peer_id):
|
||||||
peer = group.get_peer_by_id(peer_id)
|
peer = group.get_peer_by_id(peer_id)
|
||||||
if peer:
|
if peer is not None:
|
||||||
return self._get_group_peer(group, peer)
|
return self._get_group_peer(group, peer)
|
||||||
|
LOG_WARN(f"get_group_peer_by_id peer_id={peer_id}")
|
||||||
|
return None
|
||||||
|
|
||||||
def get_group_peer_by_public_key(self, group, public_key):
|
def get_group_peer_by_public_key(self, group, public_key):
|
||||||
peer = group.get_peer_by_public_key(public_key)
|
peer = group.get_peer_by_public_key(public_key)
|
||||||
|
if peer is not None:
|
||||||
|
return self._get_group_peer(group, peer)
|
||||||
|
LOG_WARN(f"get_group_peer_by_public_key public_key={public_key}")
|
||||||
|
return None
|
||||||
|
|
||||||
return self._get_group_peer(group, peer)
|
|
||||||
|
|
||||||
# -----------------------------------------------------------------------------------------------------------------
|
|
||||||
# All contacts
|
# All contacts
|
||||||
# -----------------------------------------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
def get_all(self):
|
def get_all(self):
|
||||||
return self.get_all_friends() + self.get_all_groups() + self.get_all_group_peers()
|
return self.get_all_friends() + self.get_all_groups() + self.get_all_group_peers()
|
||||||
|
|
||||||
# -----------------------------------------------------------------------------------------------------------------
|
|
||||||
# Caching
|
# Caching
|
||||||
# -----------------------------------------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
def clear_cache(self):
|
def clear_cache(self):
|
||||||
self._cache.clear()
|
self._cache.clear()
|
||||||
@ -167,9 +154,7 @@ class ContactProvider(tox_save.ToxSave):
|
|||||||
if contact_public_key in self._cache:
|
if contact_public_key in self._cache:
|
||||||
del self._cache[contact_public_key]
|
del self._cache[contact_public_key]
|
||||||
|
|
||||||
# -----------------------------------------------------------------------------------------------------------------
|
|
||||||
# Private methods
|
# Private methods
|
||||||
# -----------------------------------------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
def _get_contact_from_cache(self, public_key):
|
def _get_contact_from_cache(self, public_key):
|
||||||
return self._cache[public_key] if public_key in self._cache else None
|
return self._cache[public_key] if public_key in self._cache else None
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
# -*- mode: python; indent-tabs-mode: nil; py-indent-offset: 4; coding: utf-8 -*-
|
# -*- mode: python; indent-tabs-mode: nil; py-indent-offset: 4; coding: utf-8 -*-
|
||||||
|
|
||||||
import traceback
|
import logging
|
||||||
|
|
||||||
from contacts.friend import Friend
|
from contacts.friend import Friend
|
||||||
from contacts.group_chat import GroupChat
|
from contacts.group_chat import GroupChat
|
||||||
@ -8,23 +8,17 @@ from messenger.messages import *
|
|||||||
from common.tox_save import ToxSave
|
from common.tox_save import ToxSave
|
||||||
from contacts.group_peer_contact import GroupPeerContact
|
from contacts.group_peer_contact import GroupPeerContact
|
||||||
from groups.group_peer import GroupChatPeer
|
from groups.group_peer import GroupChatPeer
|
||||||
|
from middleware.callbacks import LOG_ERROR, LOG_WARN, LOG_INFO, LOG_DEBUG, LOG_TRACE
|
||||||
|
import toxygen_wrapper.toxcore_enums_and_consts as enums
|
||||||
|
|
||||||
# LOG=util.log
|
# LOG=util.log
|
||||||
global LOG
|
global LOG
|
||||||
import logging
|
|
||||||
LOG = logging.getLogger('app.'+__name__)
|
LOG = logging.getLogger('app.'+__name__)
|
||||||
|
|
||||||
def LOG_ERROR(l): print('ERROR_: '+l)
|
|
||||||
def LOG_WARN(l): print('WARN_: '+l)
|
|
||||||
def LOG_INFO(l): print('INFO_: '+l)
|
|
||||||
def LOG_DEBUG(l): print('DEBUG_: '+l)
|
|
||||||
def LOG_TRACE(l): pass # print('TRACE+ '+l)
|
|
||||||
|
|
||||||
|
|
||||||
UINT32_MAX = 2 ** 32 -1
|
UINT32_MAX = 2 ** 32 -1
|
||||||
|
|
||||||
def set_contact_kind(contact):
|
def set_contact_kind(contact) -> None:
|
||||||
bInvite = len(contact.name) == TOX_PUBLIC_KEY_SIZE * 2 and \
|
bInvite = len(contact.name) == enums.TOX_PUBLIC_KEY_SIZE * 2 and \
|
||||||
contact.status_message == ''
|
contact.status_message == ''
|
||||||
bBot = not bInvite and contact.name.lower().endswith(' bot')
|
bBot = not bInvite and contact.name.lower().endswith(' bot')
|
||||||
if type(contact) == Friend and bInvite:
|
if type(contact) == Friend and bInvite:
|
||||||
@ -63,7 +57,7 @@ class ContactsManager(ToxSave):
|
|||||||
self._history = history
|
self._history = history
|
||||||
self._load_contacts()
|
self._load_contacts()
|
||||||
|
|
||||||
def _log(self, s):
|
def _log(self, s) -> None:
|
||||||
try:
|
try:
|
||||||
self._ms._log(s)
|
self._ms._log(s)
|
||||||
except: pass
|
except: pass
|
||||||
@ -76,23 +70,23 @@ class ContactsManager(ToxSave):
|
|||||||
def get_curr_contact(self):
|
def get_curr_contact(self):
|
||||||
return self._contacts[self._active_contact] if self._active_contact + 1 else None
|
return self._contacts[self._active_contact] if self._active_contact + 1 else None
|
||||||
|
|
||||||
def save_profile(self):
|
def save_profile(self) -> None:
|
||||||
data = self._tox.get_savedata()
|
data = self._tox.get_savedata()
|
||||||
self._profile_manager.save_profile(data)
|
self._profile_manager.save_profile(data)
|
||||||
|
|
||||||
def is_friend_active(self, friend_number):
|
def is_friend_active(self, friend_number:int) -> bool:
|
||||||
if not self.is_active_a_friend():
|
if not self.is_active_a_friend():
|
||||||
return False
|
return False
|
||||||
|
|
||||||
return self.get_curr_contact().number == friend_number
|
return self.get_curr_contact().number == friend_number
|
||||||
|
|
||||||
def is_group_active(self, group_number):
|
def is_group_active(self, group_number) -> bool:
|
||||||
if self.is_active_a_friend():
|
if self.is_active_a_friend():
|
||||||
return False
|
return False
|
||||||
|
|
||||||
return self.get_curr_contact().number == group_number
|
return self.get_curr_contact().number == group_number
|
||||||
|
|
||||||
def is_contact_active(self, contact):
|
def is_contact_active(self, contact) -> bool:
|
||||||
if self._active_contact == -1:
|
if self._active_contact == -1:
|
||||||
# LOG.debug("No self._active_contact")
|
# LOG.debug("No self._active_contact")
|
||||||
return False
|
return False
|
||||||
@ -103,19 +97,19 @@ class ContactsManager(ToxSave):
|
|||||||
LOG.warn(f"ERROR NULL {self._contacts[self._active_contact]} {contact.tox_id}")
|
LOG.warn(f"ERROR NULL {self._contacts[self._active_contact]} {contact.tox_id}")
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
if not hasattr(contact, 'tox_id'):
|
||||||
|
LOG.warn(f"ERROR is_contact_active no contact.tox_id {type(contact)} contact={contact}")
|
||||||
|
return False
|
||||||
|
|
||||||
return self._contacts[self._active_contact].tox_id == contact.tox_id
|
return self._contacts[self._active_contact].tox_id == contact.tox_id
|
||||||
|
|
||||||
# -----------------------------------------------------------------------------------------------------------------
|
|
||||||
# Reconnection support
|
# Reconnection support
|
||||||
# -----------------------------------------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
def reset_contacts_statuses(self):
|
def reset_contacts_statuses(self) -> None:
|
||||||
for contact in self._contacts:
|
for contact in self._contacts:
|
||||||
contact.status = None
|
contact.status = None
|
||||||
|
|
||||||
# -----------------------------------------------------------------------------------------------------------------
|
|
||||||
# Work with active friend
|
# Work with active friend
|
||||||
# -----------------------------------------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
def get_active(self):
|
def get_active(self):
|
||||||
return self._active_contact
|
return self._active_contact
|
||||||
@ -150,7 +144,11 @@ class ContactsManager(ToxSave):
|
|||||||
current_contact.curr_text = self._screen.messageEdit.toPlainText()
|
current_contact.curr_text = self._screen.messageEdit.toPlainText()
|
||||||
except:
|
except:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
# IndexError: list index out of range
|
# IndexError: list index out of range
|
||||||
|
if value >= len(self._contacts):
|
||||||
|
LOG.warn("CM.set_active value too big: {{self._contacts}}")
|
||||||
|
return
|
||||||
contact = self._contacts[value]
|
contact = self._contacts[value]
|
||||||
self._subscribe_to_events(contact)
|
self._subscribe_to_events(contact)
|
||||||
contact.remove_invalid_unsent_files()
|
contact.remove_invalid_unsent_files()
|
||||||
@ -179,9 +177,8 @@ class ContactsManager(ToxSave):
|
|||||||
# self._screen.call_finished()
|
# self._screen.call_finished()
|
||||||
self._set_current_contact_data(contact)
|
self._set_current_contact_data(contact)
|
||||||
self._active_contact_changed(contact)
|
self._active_contact_changed(contact)
|
||||||
except Exception as ex: # no friend found. ignore
|
except Exception as e: # no friend found. ignore
|
||||||
LOG.warn(f"no friend found. Friend value: {value!s}")
|
LOG.warn(f"CM.set_active EXCEPTION value:{value} len={len(self._contacts)} {e}")
|
||||||
LOG.error('in set active: ' + str(ex))
|
|
||||||
# gulp raise
|
# gulp raise
|
||||||
|
|
||||||
active_contact = property(get_active, set_active)
|
active_contact = property(get_active, set_active)
|
||||||
@ -204,9 +201,7 @@ class ContactsManager(ToxSave):
|
|||||||
def is_active_a_group_chat_peer(self):
|
def is_active_a_group_chat_peer(self):
|
||||||
return type(self.get_curr_contact()) is GroupPeerContact
|
return type(self.get_curr_contact()) is GroupPeerContact
|
||||||
|
|
||||||
# -----------------------------------------------------------------------------------------------------------------
|
|
||||||
# Filtration
|
# Filtration
|
||||||
# -----------------------------------------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
def filtration_and_sorting(self, sorting=0, filter_str=''):
|
def filtration_and_sorting(self, sorting=0, filter_str=''):
|
||||||
"""
|
"""
|
||||||
@ -259,6 +254,9 @@ class ContactsManager(ToxSave):
|
|||||||
for index, contact in enumerate(self._contacts):
|
for index, contact in enumerate(self._contacts):
|
||||||
list_item = self._screen.friends_list.item(index)
|
list_item = self._screen.friends_list.item(index)
|
||||||
item_widget = self._screen.friends_list.itemWidget(list_item)
|
item_widget = self._screen.friends_list.itemWidget(list_item)
|
||||||
|
if not item_widget:
|
||||||
|
LOG_WARN("CM.filtration_and_sorting( item_widget is NULL")
|
||||||
|
continue
|
||||||
contact.set_widget(item_widget)
|
contact.set_widget(item_widget)
|
||||||
|
|
||||||
for index, friend in enumerate(self._contacts):
|
for index, friend in enumerate(self._contacts):
|
||||||
@ -286,9 +284,7 @@ class ContactsManager(ToxSave):
|
|||||||
"""
|
"""
|
||||||
self.filtration_and_sorting(self._sorting, self._filter_string)
|
self.filtration_and_sorting(self._sorting, self._filter_string)
|
||||||
|
|
||||||
# -----------------------------------------------------------------------------------------------------------------
|
|
||||||
# Contact getters
|
# Contact getters
|
||||||
# -----------------------------------------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
def get_friend_by_number(self, number):
|
def get_friend_by_number(self, number):
|
||||||
return list(filter(lambda c: c.number == number and type(c) is Friend, self._contacts))[0]
|
return list(filter(lambda c: c.number == number and type(c) is Friend, self._contacts))[0]
|
||||||
@ -299,15 +295,16 @@ class ContactsManager(ToxSave):
|
|||||||
def get_or_create_group_peer_contact(self, group_number, peer_id):
|
def get_or_create_group_peer_contact(self, group_number, peer_id):
|
||||||
group = self.get_group_by_number(group_number)
|
group = self.get_group_by_number(group_number)
|
||||||
peer = group.get_peer_by_id(peer_id)
|
peer = group.get_peer_by_id(peer_id)
|
||||||
if peer: # broken?
|
if peer is None:
|
||||||
if not hasattr(peer, 'public_key') or not peer.public_key:
|
LOG.warn(f'get_or_create_group_peer_contact group_number={group_number} peer_id={peer_id} peer={peer}')
|
||||||
LOG.error(f'no peer public_key ' + repr(dir(peer)))
|
return None
|
||||||
else:
|
LOG.debug(f'get_or_create_group_peer_contact group_number={group_number} peer_id={peer_id} peer={peer}')
|
||||||
if not self.check_if_contact_exists(peer.public_key):
|
if not self.check_if_contact_exists(peer.public_key):
|
||||||
self.add_group_peer(group, peer)
|
contact = self.add_group_peer(group, peer)
|
||||||
return self.get_contact_by_tox_id(peer.public_key)
|
# dunno
|
||||||
else:
|
return contact
|
||||||
LOG.warn(f'no peer group_number={group_number} peer_id={peer_id}')
|
# me - later wrong kind of object?
|
||||||
|
return self.get_contact_by_tox_id(peer.public_key)
|
||||||
|
|
||||||
def check_if_contact_exists(self, tox_id):
|
def check_if_contact_exists(self, tox_id):
|
||||||
return any(filter(lambda c: c.tox_id == tox_id, self._contacts))
|
return any(filter(lambda c: c.tox_id == tox_id, self._contacts))
|
||||||
@ -324,9 +321,7 @@ class ContactsManager(ToxSave):
|
|||||||
def is_active_online(self):
|
def is_active_online(self):
|
||||||
return self._active_contact + 1 and self.get_curr_contact().status is not None
|
return self._active_contact + 1 and self.get_curr_contact().status is not None
|
||||||
|
|
||||||
# -----------------------------------------------------------------------------------------------------------------
|
|
||||||
# Work with friends (remove, block, set alias, get public key)
|
# Work with friends (remove, block, set alias, get public key)
|
||||||
# -----------------------------------------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
def set_alias(self, num):
|
def set_alias(self, num):
|
||||||
"""
|
"""
|
||||||
@ -386,8 +381,8 @@ class ContactsManager(ToxSave):
|
|||||||
"""
|
"""
|
||||||
Block user with specified tox id (or public key) - delete from friends list and ignore friend requests
|
Block user with specified tox id (or public key) - delete from friends list and ignore friend requests
|
||||||
"""
|
"""
|
||||||
tox_id = tox_id[:TOX_PUBLIC_KEY_SIZE * 2]
|
tox_id = tox_id[:enums.TOX_PUBLIC_KEY_SIZE * 2]
|
||||||
if tox_id == self._tox.self_get_address()[:TOX_PUBLIC_KEY_SIZE * 2]:
|
if tox_id == self._tox.self_get_address()[:enums.TOX_PUBLIC_KEY_SIZE * 2]:
|
||||||
return
|
return
|
||||||
if tox_id not in self._settings['blocked']:
|
if tox_id not in self._settings['blocked']:
|
||||||
self._settings['blocked'].append(tox_id)
|
self._settings['blocked'].append(tox_id)
|
||||||
@ -411,9 +406,7 @@ class ContactsManager(ToxSave):
|
|||||||
self.add_friend(tox_id)
|
self.add_friend(tox_id)
|
||||||
self.save_profile()
|
self.save_profile()
|
||||||
|
|
||||||
# -----------------------------------------------------------------------------------------------------------------
|
|
||||||
# Groups support
|
# Groups support
|
||||||
# -----------------------------------------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
def get_group_chats(self):
|
def get_group_chats(self):
|
||||||
return list(filter(lambda c: type(c) is GroupChat, self._contacts))
|
return list(filter(lambda c: type(c) is GroupChat, self._contacts))
|
||||||
@ -421,9 +414,10 @@ class ContactsManager(ToxSave):
|
|||||||
def add_group(self, group_number):
|
def add_group(self, group_number):
|
||||||
index = len(self._contacts)
|
index = len(self._contacts)
|
||||||
group = self._contact_provider.get_group_by_number(group_number)
|
group = self._contact_provider.get_group_by_number(group_number)
|
||||||
# group num >= 0?
|
|
||||||
if group is None:
|
if group is None:
|
||||||
LOG.warn(f"CM.add_group: NO group {group_number}")
|
LOG.warn(f"CM.add_group: NULL group from group_number={group_number}")
|
||||||
|
elif type(group) == int and group < 0:
|
||||||
|
LOG.warn(f"CM.add_group: NO group from group={group} group_number={group_number}")
|
||||||
else:
|
else:
|
||||||
LOG.info(f"CM.add_group: Adding group {group._name}")
|
LOG.info(f"CM.add_group: Adding group {group._name}")
|
||||||
self._contacts.append(group)
|
self._contacts.append(group)
|
||||||
@ -440,18 +434,17 @@ class ContactsManager(ToxSave):
|
|||||||
num = self._contacts.index(group)
|
num = self._contacts.index(group)
|
||||||
self._delete_contact(num)
|
self._delete_contact(num)
|
||||||
|
|
||||||
# -----------------------------------------------------------------------------------------------------------------
|
|
||||||
# Groups private messaging
|
# Groups private messaging
|
||||||
# -----------------------------------------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
def add_group_peer(self, group, peer):
|
def add_group_peer(self, group, peer):
|
||||||
contact = self._contact_provider.get_group_peer_by_id(group, peer.id)
|
contact = self._contact_provider.get_group_peer_by_id(group, peer.id)
|
||||||
if self.check_if_contact_exists(contact.tox_id):
|
if self.check_if_contact_exists(contact.tox_id):
|
||||||
return
|
return contact
|
||||||
contact._kind = 'grouppeer'
|
contact._kind = 'grouppeer'
|
||||||
self._contacts.append(contact)
|
self._contacts.append(contact)
|
||||||
contact.reset_avatar(self._settings['identicons'])
|
contact.reset_avatar(self._settings['identicons'])
|
||||||
self._save_profile()
|
self._save_profile()
|
||||||
|
return contact
|
||||||
|
|
||||||
def remove_group_peer_by_id(self, group, peer_id):
|
def remove_group_peer_by_id(self, group, peer_id):
|
||||||
peer = group.get_peer_by_id(peer_id)
|
peer = group.get_peer_by_id(peer_id)
|
||||||
@ -484,9 +477,7 @@ class ContactsManager(ToxSave):
|
|||||||
|
|
||||||
return suggested_names[0]
|
return suggested_names[0]
|
||||||
|
|
||||||
# -----------------------------------------------------------------------------------------------------------------
|
|
||||||
# Friend requests
|
# Friend requests
|
||||||
# -----------------------------------------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
def send_friend_request(self, sToxPkOrId, message):
|
def send_friend_request(self, sToxPkOrId, message):
|
||||||
"""
|
"""
|
||||||
@ -498,14 +489,14 @@ class ContactsManager(ToxSave):
|
|||||||
retval = ''
|
retval = ''
|
||||||
try:
|
try:
|
||||||
message = message or 'Hello! Add me to your contact list please'
|
message = message or 'Hello! Add me to your contact list please'
|
||||||
if len(sToxPkOrId) == TOX_PUBLIC_KEY_SIZE * 2: # public key
|
if len(sToxPkOrId) == enums.TOX_PUBLIC_KEY_SIZE * 2: # public key
|
||||||
self.add_friend(sToxPkOrId)
|
self.add_friend(sToxPkOrId)
|
||||||
title = 'Friend added'
|
title = 'Friend added'
|
||||||
text = 'Friend added without sending friend request'
|
text = 'Friend added without sending friend request'
|
||||||
else:
|
else:
|
||||||
num = self._tox.friend_add(sToxPkOrId, message.encode('utf-8'))
|
num = self._tox.friend_add(sToxPkOrId, message.encode('utf-8'))
|
||||||
if num < UINT32_MAX:
|
if num < UINT32_MAX:
|
||||||
tox_pk = sToxPkOrId[:TOX_PUBLIC_KEY_SIZE * 2]
|
tox_pk = sToxPkOrId[:enums.TOX_PUBLIC_KEY_SIZE * 2]
|
||||||
self._add_friend(tox_pk)
|
self._add_friend(tox_pk)
|
||||||
self.update_filtration()
|
self.update_filtration()
|
||||||
title = 'Friend added'
|
title = 'Friend added'
|
||||||
@ -550,9 +541,7 @@ class ContactsManager(ToxSave):
|
|||||||
def can_send_typing_notification(self):
|
def can_send_typing_notification(self):
|
||||||
return self._settings['typing_notifications'] and not self.is_active_a_group_chat_peer()
|
return self._settings['typing_notifications'] and not self.is_active_a_group_chat_peer()
|
||||||
|
|
||||||
# -----------------------------------------------------------------------------------------------------------------
|
|
||||||
# Contacts numbers update
|
# Contacts numbers update
|
||||||
# -----------------------------------------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
def update_friends_numbers(self):
|
def update_friends_numbers(self):
|
||||||
for friend in self._contact_provider.get_all_friends():
|
for friend in self._contact_provider.get_all_friends():
|
||||||
@ -580,9 +569,7 @@ class ContactsManager(ToxSave):
|
|||||||
for group in groups:
|
for group in groups:
|
||||||
group.remove_all_peers_except_self()
|
group.remove_all_peers_except_self()
|
||||||
|
|
||||||
# -----------------------------------------------------------------------------------------------------------------
|
|
||||||
# Private methods
|
# Private methods
|
||||||
# -----------------------------------------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
def _load_contacts(self):
|
def _load_contacts(self):
|
||||||
self._load_friends()
|
self._load_friends()
|
||||||
@ -607,9 +594,7 @@ class ContactsManager(ToxSave):
|
|||||||
def _load_groups(self):
|
def _load_groups(self):
|
||||||
self._contacts.extend(self._contact_provider.get_all_groups())
|
self._contacts.extend(self._contact_provider.get_all_groups())
|
||||||
|
|
||||||
# -----------------------------------------------------------------------------------------------------------------
|
|
||||||
# Current contact subscriptions
|
# Current contact subscriptions
|
||||||
# -----------------------------------------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
def _subscribe_to_events(self, contact):
|
def _subscribe_to_events(self, contact):
|
||||||
contact.name_changed_event.add_callback(self._current_contact_name_changed)
|
contact.name_changed_event.add_callback(self._current_contact_name_changed)
|
||||||
@ -664,7 +649,7 @@ class ContactsManager(ToxSave):
|
|||||||
try:
|
try:
|
||||||
index = list(map(lambda x: x[0], self._settings['friends_aliases'])).index(contact.tox_id)
|
index = list(map(lambda x: x[0], self._settings['friends_aliases'])).index(contact.tox_id)
|
||||||
del self._settings['friends_aliases'][index]
|
del self._settings['friends_aliases'][index]
|
||||||
except:
|
except Exception as e:
|
||||||
pass
|
pass
|
||||||
if contact.tox_id in self._settings['notes']:
|
if contact.tox_id in self._settings['notes']:
|
||||||
del self._settings['notes'][contact.tox_id]
|
del self._settings['notes'][contact.tox_id]
|
||||||
|
@ -1,9 +1,11 @@
|
|||||||
|
# -*- mode: python; indent-tabs-mode: nil; py-indent-offset: 4; coding: utf-8 -*-
|
||||||
|
|
||||||
|
import os
|
||||||
|
|
||||||
from contacts import contact, common
|
from contacts import contact, common
|
||||||
from messenger.messages import *
|
from messenger.messages import *
|
||||||
import os
|
|
||||||
from contacts.contact_menu import *
|
from contacts.contact_menu import *
|
||||||
|
|
||||||
|
|
||||||
class Friend(contact.Contact):
|
class Friend(contact.Contact):
|
||||||
"""
|
"""
|
||||||
Friend in list of friends.
|
Friend in list of friends.
|
||||||
@ -14,9 +16,7 @@ class Friend(contact.Contact):
|
|||||||
self._receipts = 0
|
self._receipts = 0
|
||||||
self._typing_notification_handler = common.FriendTypingNotificationHandler(number)
|
self._typing_notification_handler = common.FriendTypingNotificationHandler(number)
|
||||||
|
|
||||||
# -----------------------------------------------------------------------------------------------------------------
|
|
||||||
# File transfers support
|
# File transfers support
|
||||||
# -----------------------------------------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
def insert_inline(self, before_message_id, inline):
|
def insert_inline(self, before_message_id, inline):
|
||||||
"""
|
"""
|
||||||
@ -29,7 +29,7 @@ class Friend(contact.Contact):
|
|||||||
self._corr.insert(i, inline)
|
self._corr.insert(i, inline)
|
||||||
return i - len(self._corr)
|
return i - len(self._corr)
|
||||||
except:
|
except:
|
||||||
pass
|
return -1
|
||||||
|
|
||||||
def get_unsent_files(self):
|
def get_unsent_files(self):
|
||||||
messages = filter(lambda m: type(m) is UnsentFileMessage, self._corr)
|
messages = filter(lambda m: type(m) is UnsentFileMessage, self._corr)
|
||||||
@ -52,23 +52,17 @@ class Friend(contact.Contact):
|
|||||||
self._corr = list(filter(lambda m: not (type(m) is UnsentFileMessage and m.message_id == message_id),
|
self._corr = list(filter(lambda m: not (type(m) is UnsentFileMessage and m.message_id == message_id),
|
||||||
self._corr))
|
self._corr))
|
||||||
|
|
||||||
# -----------------------------------------------------------------------------------------------------------------
|
|
||||||
# Full status
|
# Full status
|
||||||
# -----------------------------------------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
def get_full_status(self):
|
def get_full_status(self):
|
||||||
return self._status_message
|
return self._status_message
|
||||||
|
|
||||||
# -----------------------------------------------------------------------------------------------------------------
|
|
||||||
# Typing notifications
|
# Typing notifications
|
||||||
# -----------------------------------------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
def get_typing_notification_handler(self):
|
def get_typing_notification_handler(self):
|
||||||
return self._typing_notification_handler
|
return self._typing_notification_handler
|
||||||
|
|
||||||
# -----------------------------------------------------------------------------------------------------------------
|
|
||||||
# Context menu support
|
# Context menu support
|
||||||
# -----------------------------------------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
def get_context_menu_generator(self):
|
def get_context_menu_generator(self):
|
||||||
return FriendMenuGenerator(self)
|
return FriendMenuGenerator(self)
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
from common.tox_save import ToxSave
|
# -*- mode: python; indent-tabs-mode: nil; py-indent-offset: 4; coding: utf-8 -*-
|
||||||
from contacts.friend import Friend
|
|
||||||
|
|
||||||
|
from contacts.friend import Friend
|
||||||
|
from common.tox_save import ToxSave
|
||||||
|
|
||||||
class FriendFactory(ToxSave):
|
class FriendFactory(ToxSave):
|
||||||
|
|
||||||
@ -15,7 +16,7 @@ class FriendFactory(ToxSave):
|
|||||||
friend_number = self._tox.friend_by_public_key(public_key)
|
friend_number = self._tox.friend_by_public_key(public_key)
|
||||||
return self.create_friend_by_number(friend_number)
|
return self.create_friend_by_number(friend_number)
|
||||||
|
|
||||||
def create_friend_by_number(self, friend_number):
|
def create_friend_by_number(self, friend_number:int):
|
||||||
aliases = self._settings['friends_aliases']
|
aliases = self._settings['friends_aliases']
|
||||||
sToxPk = self._tox.friend_get_public_key(friend_number)
|
sToxPk = self._tox.friend_get_public_key(friend_number)
|
||||||
assert sToxPk, sToxPk
|
assert sToxPk, sToxPk
|
||||||
@ -32,9 +33,7 @@ class FriendFactory(ToxSave):
|
|||||||
|
|
||||||
return friend
|
return friend
|
||||||
|
|
||||||
# -----------------------------------------------------------------------------------------------------------------
|
|
||||||
# Private methods
|
# Private methods
|
||||||
# -----------------------------------------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
def _create_friend_item(self):
|
def _create_friend_item(self):
|
||||||
"""
|
"""
|
||||||
|
@ -4,18 +4,14 @@ from contacts import contact
|
|||||||
from contacts.contact_menu import GroupMenuGenerator
|
from contacts.contact_menu import GroupMenuGenerator
|
||||||
import utils.util as util
|
import utils.util as util
|
||||||
from groups.group_peer import GroupChatPeer
|
from groups.group_peer import GroupChatPeer
|
||||||
from wrapper import toxcore_enums_and_consts as constants
|
from toxygen_wrapper import toxcore_enums_and_consts as constants
|
||||||
from common.tox_save import ToxSave
|
from common.tox_save import ToxSave
|
||||||
from groups.group_ban import GroupBan
|
from groups.group_ban import GroupBan
|
||||||
|
|
||||||
global LOG
|
global LOG
|
||||||
import logging
|
import logging
|
||||||
LOG = logging.getLogger(__name__)
|
LOG = logging.getLogger(__name__)
|
||||||
def LOG_ERROR(l): print('ERROR_: '+l)
|
from av.calls import LOG_ERROR, LOG_WARN, LOG_INFO, LOG_DEBUG, LOG_TRACE
|
||||||
def LOG_WARN(l): print('WARN_: '+l)
|
|
||||||
def LOG_INFO(l): print('INFO_: '+l)
|
|
||||||
def LOG_DEBUG(l): print('DEBUG_: '+l)
|
|
||||||
def LOG_TRACE(l): pass # print('TRACE+ '+l)
|
|
||||||
|
|
||||||
class GroupChat(contact.Contact, ToxSave):
|
class GroupChat(contact.Contact, ToxSave):
|
||||||
|
|
||||||
@ -35,9 +31,7 @@ class GroupChat(contact.Contact, ToxSave):
|
|||||||
def get_context_menu_generator(self):
|
def get_context_menu_generator(self):
|
||||||
return GroupMenuGenerator(self)
|
return GroupMenuGenerator(self)
|
||||||
|
|
||||||
# -----------------------------------------------------------------------------------------------------------------
|
|
||||||
# Properties
|
# Properties
|
||||||
# -----------------------------------------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
def get_is_private(self):
|
def get_is_private(self):
|
||||||
return self._is_private
|
return self._is_private
|
||||||
@ -63,9 +57,7 @@ class GroupChat(contact.Contact, ToxSave):
|
|||||||
|
|
||||||
peers_limit = property(get_peers_limit, set_peers_limit)
|
peers_limit = property(get_peers_limit, set_peers_limit)
|
||||||
|
|
||||||
# -----------------------------------------------------------------------------------------------------------------
|
|
||||||
# Peers methods
|
# Peers methods
|
||||||
# -----------------------------------------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
def get_self_peer(self):
|
def get_self_peer(self):
|
||||||
return self._peers[0]
|
return self._peers[0]
|
||||||
@ -88,13 +80,15 @@ class GroupChat(contact.Contact, ToxSave):
|
|||||||
LOG_WARN(f"add_peer id={peer_id} > {self._peers_limit}")
|
LOG_WARN(f"add_peer id={peer_id} > {self._peers_limit}")
|
||||||
return
|
return
|
||||||
|
|
||||||
LOG_TRACE(f"add_peer id={peer_id}")
|
status_message = f"Private in {self.name}"
|
||||||
|
LOG_TRACE(f"GC.add_peer id={peer_id} status_message={status_message}")
|
||||||
peer = GroupChatPeer(peer_id,
|
peer = GroupChatPeer(peer_id,
|
||||||
self._tox.group_peer_get_name(self._number, peer_id),
|
self._tox.group_peer_get_name(self._number, peer_id),
|
||||||
self._tox.group_peer_get_status(self._number, peer_id),
|
self._tox.group_peer_get_status(self._number, peer_id),
|
||||||
self._tox.group_peer_get_role(self._number, peer_id),
|
self._tox.group_peer_get_role(self._number, peer_id),
|
||||||
self._tox.group_peer_get_public_key(self._number, peer_id),
|
self._tox.group_peer_get_public_key(self._number, peer_id),
|
||||||
is_current_user)
|
is_current_user,
|
||||||
|
status_message=status_message)
|
||||||
self._peers.append(peer)
|
self._peers.append(peer)
|
||||||
|
|
||||||
def remove_peer(self, peer_id):
|
def remove_peer(self, peer_id):
|
||||||
@ -113,7 +107,7 @@ class GroupChat(contact.Contact, ToxSave):
|
|||||||
return peers[0]
|
return peers[0]
|
||||||
else:
|
else:
|
||||||
LOG_WARN(f"get_peer_by_id empty peers for {peer_id}")
|
LOG_WARN(f"get_peer_by_id empty peers for {peer_id}")
|
||||||
return []
|
return None
|
||||||
|
|
||||||
def get_peer_by_public_key(self, public_key):
|
def get_peer_by_public_key(self, public_key):
|
||||||
peers = list(filter(lambda p: p.public_key == public_key, self._peers))
|
peers = list(filter(lambda p: p.public_key == public_key, self._peers))
|
||||||
@ -123,7 +117,7 @@ class GroupChat(contact.Contact, ToxSave):
|
|||||||
return peers[0]
|
return peers[0]
|
||||||
else:
|
else:
|
||||||
LOG_WARN(f"get_peer_by_public_key empty peers for {public_key}")
|
LOG_WARN(f"get_peer_by_public_key empty peers for {public_key}")
|
||||||
return []
|
return None
|
||||||
|
|
||||||
def remove_all_peers_except_self(self):
|
def remove_all_peers_except_self(self):
|
||||||
self._peers = self._peers[:1]
|
self._peers = self._peers[:1]
|
||||||
@ -156,9 +150,7 @@ class GroupChat(contact.Contact, ToxSave):
|
|||||||
#
|
#
|
||||||
bans = property(get_bans)
|
bans = property(get_bans)
|
||||||
|
|
||||||
# -----------------------------------------------------------------------------------------------------------------
|
|
||||||
# Private methods
|
# Private methods
|
||||||
# -----------------------------------------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _get_default_avatar_path():
|
def _get_default_avatar_path():
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
from contacts.group_chat import GroupChat
|
from contacts.group_chat import GroupChat
|
||||||
from common.tox_save import ToxSave
|
from common.tox_save import ToxSave
|
||||||
import wrapper.toxcore_enums_and_consts as constants
|
import toxygen_wrapper.toxcore_enums_and_consts as constants
|
||||||
|
|
||||||
global LOG
|
global LOG
|
||||||
import logging
|
import logging
|
||||||
@ -43,9 +43,7 @@ class GroupFactory(ToxSave):
|
|||||||
|
|
||||||
return group
|
return group
|
||||||
|
|
||||||
# -----------------------------------------------------------------------------------------------------------------
|
|
||||||
# Private methods
|
# Private methods
|
||||||
# -----------------------------------------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
def _create_group_item(self):
|
def _create_group_item(self):
|
||||||
"""
|
"""
|
||||||
@ -55,7 +53,7 @@ class GroupFactory(ToxSave):
|
|||||||
return self._items_factory.create_contact_item()
|
return self._items_factory.create_contact_item()
|
||||||
|
|
||||||
def _get_group_number_by_chat_id(self, chat_id):
|
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:
|
if self._tox.group_get_chat_id(i) == chat_id:
|
||||||
return i
|
return i
|
||||||
return -1
|
return -1
|
||||||
|
@ -1,11 +1,13 @@
|
|||||||
|
# -*- mode: python; indent-tabs-mode: nil; py-indent-offset: 4; coding: utf-8 -*-
|
||||||
|
|
||||||
import contacts.contact
|
import contacts.contact
|
||||||
from contacts.contact_menu import GroupPeerMenuGenerator
|
from contacts.contact_menu import GroupPeerMenuGenerator
|
||||||
|
|
||||||
|
|
||||||
class GroupPeerContact(contacts.contact.Contact):
|
class GroupPeerContact(contacts.contact.Contact):
|
||||||
|
|
||||||
def __init__(self, profile_manager, message_getter, peer_number, name, widget, tox_id, group_pk):
|
def __init__(self, profile_manager, message_getter, peer_number, name, widget, tox_id, group_pk, status_message=None):
|
||||||
super().__init__(profile_manager, message_getter, peer_number, name, str(), widget, tox_id)
|
if status_message is None: status_message=str()
|
||||||
|
super().__init__(profile_manager, message_getter, peer_number, name, status_message, widget, tox_id)
|
||||||
self._group_pk = group_pk
|
self._group_pk = group_pk
|
||||||
|
|
||||||
def get_group_pk(self):
|
def get_group_pk(self):
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
|
# -*- mode: python; indent-tabs-mode: nil; py-indent-offset: 4; coding: utf-8 -*-
|
||||||
from common.tox_save import ToxSave
|
from common.tox_save import ToxSave
|
||||||
from contacts.group_peer_contact import GroupPeerContact
|
from contacts.group_peer_contact import GroupPeerContact
|
||||||
|
|
||||||
|
|
||||||
class GroupPeerFactory(ToxSave):
|
class GroupPeerFactory(ToxSave):
|
||||||
|
|
||||||
def __init__(self, tox, profile_manager, db, items_factory):
|
def __init__(self, tox, profile_manager, db, items_factory):
|
||||||
@ -14,7 +14,10 @@ class GroupPeerFactory(ToxSave):
|
|||||||
item = self._create_group_peer_item()
|
item = self._create_group_peer_item()
|
||||||
message_getter = self._db.messages_getter(peer.public_key)
|
message_getter = self._db.messages_getter(peer.public_key)
|
||||||
group_peer_contact = GroupPeerContact(self._profile_manager, message_getter, peer.id, peer.name,
|
group_peer_contact = GroupPeerContact(self._profile_manager, message_getter, peer.id, peer.name,
|
||||||
item, peer.public_key, group.tox_id)
|
item,
|
||||||
|
peer.public_key,
|
||||||
|
group.tox_id,
|
||||||
|
status_message=peer.status_message)
|
||||||
group_peer_contact.status = peer.status
|
group_peer_contact.status = peer.status
|
||||||
|
|
||||||
return group_peer_contact
|
return group_peer_contact
|
||||||
|
@ -6,6 +6,7 @@ import common.tox_save as tox_save
|
|||||||
from middleware.threads import invoke_in_main_thread
|
from middleware.threads import invoke_in_main_thread
|
||||||
|
|
||||||
iUMAXINT = 4294967295
|
iUMAXINT = 4294967295
|
||||||
|
iRECONNECT = 50
|
||||||
|
|
||||||
global LOG
|
global LOG
|
||||||
import logging
|
import logging
|
||||||
@ -15,7 +16,7 @@ class Profile(basecontact.BaseContact, tox_save.ToxSave):
|
|||||||
"""
|
"""
|
||||||
Profile of current toxygen user.
|
Profile of current toxygen user.
|
||||||
"""
|
"""
|
||||||
def __init__(self, profile_manager, tox, screen, contacts_provider, reset_action):
|
def __init__(self, profile_manager, tox, screen, contacts_provider, reset_action, app=None):
|
||||||
"""
|
"""
|
||||||
:param tox: tox instance
|
:param tox: tox instance
|
||||||
:param screen: ref to main screen
|
:param screen: ref to main screen
|
||||||
@ -34,34 +35,33 @@ class Profile(basecontact.BaseContact, tox_save.ToxSave):
|
|||||||
self._reset_action = reset_action
|
self._reset_action = reset_action
|
||||||
self._waiting_for_reconnection = False
|
self._waiting_for_reconnection = False
|
||||||
self._timer = None
|
self._timer = None
|
||||||
|
self._app = app
|
||||||
|
|
||||||
# -----------------------------------------------------------------------------------------------------------------
|
|
||||||
# Edit current user's data
|
# Edit current user's data
|
||||||
# -----------------------------------------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
def change_status(self):
|
def change_status(self) -> None:
|
||||||
"""
|
"""
|
||||||
Changes status of user (online, away, busy)
|
Changes status of user (online, away, busy)
|
||||||
"""
|
"""
|
||||||
if self._status is not None:
|
if self._status is not None:
|
||||||
self.set_status((self._status + 1) % 3)
|
self.set_status((self._status + 1) % 3)
|
||||||
|
|
||||||
def set_status(self, status):
|
def set_status(self, status) -> None:
|
||||||
super().set_status(status)
|
super().set_status(status)
|
||||||
if status is not None:
|
if status is not None:
|
||||||
self._tox.self_set_status(status)
|
self._tox.self_set_status(status)
|
||||||
elif not self._waiting_for_reconnection:
|
elif not self._waiting_for_reconnection:
|
||||||
self._waiting_for_reconnection = True
|
self._waiting_for_reconnection = True
|
||||||
self._timer = threading.Timer(50, self._reconnect)
|
self._timer = threading.Timer(iRECONNECT, self._reconnect)
|
||||||
self._timer.start()
|
self._timer.start()
|
||||||
|
|
||||||
def set_name(self, value):
|
def set_name(self, value) -> None:
|
||||||
if self.name == value:
|
if self.name == value:
|
||||||
return
|
return
|
||||||
super().set_name(value)
|
super().set_name(value)
|
||||||
self._tox.self_set_name(self._name)
|
self._tox.self_set_name(self._name)
|
||||||
|
|
||||||
def set_status_message(self, value):
|
def set_status_message(self, value) -> None:
|
||||||
super().set_status_message(value)
|
super().set_status_message(value)
|
||||||
self._tox.self_set_status_message(self._status_message)
|
self._tox.self_set_status_message(self._status_message)
|
||||||
|
|
||||||
@ -72,23 +72,36 @@ class Profile(basecontact.BaseContact, tox_save.ToxSave):
|
|||||||
self._sToxId = self._tox.self_get_address()
|
self._sToxId = self._tox.self_get_address()
|
||||||
return self._sToxId
|
return self._sToxId
|
||||||
|
|
||||||
# -----------------------------------------------------------------------------------------------------------------
|
|
||||||
# Reset
|
# Reset
|
||||||
# -----------------------------------------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
def restart(self):
|
def restart(self) -> None:
|
||||||
"""
|
"""
|
||||||
Recreate tox instance
|
Recreate tox instance
|
||||||
"""
|
"""
|
||||||
self.status = None
|
self.status = None
|
||||||
invoke_in_main_thread(self._reset_action)
|
invoke_in_main_thread(self._reset_action)
|
||||||
|
|
||||||
def _reconnect(self):
|
def _reconnect(self) -> None:
|
||||||
self._waiting_for_reconnection = False
|
self._waiting_for_reconnection = False
|
||||||
|
if self._app and self._app.bAppExiting:
|
||||||
|
# dont do anything after the app has been shipped
|
||||||
|
# there's a segv that results
|
||||||
|
return
|
||||||
contacts = self._contacts_provider.get_all_friends()
|
contacts = self._contacts_provider.get_all_friends()
|
||||||
all_friends_offline = all(list(map(lambda x: x.status is None, contacts)))
|
all_friends_offline = all(list(map(lambda x: x.status is None, contacts)))
|
||||||
if self.status is None or (all_friends_offline and len(contacts)):
|
if self.status is None or (all_friends_offline and len(contacts)):
|
||||||
self._waiting_for_reconnection = True
|
self._waiting_for_reconnection = True
|
||||||
self.restart()
|
self.restart()
|
||||||
self._timer = threading.Timer(50, self._reconnect)
|
self._timer = threading.Timer(iRECONNECT, self._reconnect)
|
||||||
self._timer.start()
|
self._timer.start()
|
||||||
|
|
||||||
|
# Current thread 0x00007901a13ccb80 (most recent call first):
|
||||||
|
# File "/usr/local/lib/python3.11/site-packages/toxygen_wrapper/tox.py", line 826 in self_get_friend_list_size
|
||||||
|
# File "/usr/local/lib/python3.11/site-packages/toxygen_wrapper/tox.py", line 838 in self_get_friend_list
|
||||||
|
# File "/mnt/o/var/local/src/toxygen/toxygen/contacts/contact_provider.py", line 45 in get_all_friends
|
||||||
|
# File "/mnt/o/var/local/src/toxygen/toxygen/contacts/profile.py", line 90 in _reconnect
|
||||||
|
# File "/usr/lib/python3.11/threading.py", line 1401 in run
|
||||||
|
# File "/usr/lib/python3.11/threading.py", line 1045 in _bootstrap_inner
|
||||||
|
# File "/usr/lib/python3.11/threading.py", line 1002 in _bootstrap
|
||||||
|
#
|
||||||
|
|
||||||
|
@ -1,11 +1,15 @@
|
|||||||
|
# -*- mode: python; indent-tabs-mode: nil; py-indent-offset: 4; coding: utf-8 -*-
|
||||||
|
|
||||||
|
import os
|
||||||
from os import chdir, remove, rename
|
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 time import time
|
||||||
|
|
||||||
from common.event import Event
|
from common.event import Event
|
||||||
from middleware.threads import invoke_in_main_thread
|
from middleware.threads import invoke_in_main_thread
|
||||||
from wrapper.tox import Tox
|
from toxygen_wrapper.tox import Tox
|
||||||
from wrapper.toxcore_enums_and_consts import TOX_FILE_CONTROL, TOX_FILE_KIND
|
from toxygen_wrapper.toxcore_enums_and_consts import TOX_FILE_KIND, TOX_FILE_CONTROL
|
||||||
|
from middleware.callbacks import LOG_ERROR, LOG_WARN, LOG_INFO, LOG_DEBUG, LOG_TRACE
|
||||||
|
|
||||||
FILE_TRANSFER_STATE = {
|
FILE_TRANSFER_STATE = {
|
||||||
'RUNNING': 0,
|
'RUNNING': 0,
|
||||||
@ -78,6 +82,7 @@ class FileTransfer:
|
|||||||
|
|
||||||
def get_file_id(self):
|
def get_file_id(self):
|
||||||
return self._file_id
|
return self._file_id
|
||||||
|
#? return self._tox.file_get_file_id(self._friend_number, self._file_number)
|
||||||
|
|
||||||
file_id = property(get_file_id)
|
file_id = property(get_file_id)
|
||||||
|
|
||||||
@ -112,9 +117,6 @@ class FileTransfer:
|
|||||||
if self._tox.file_control(self._friend_number, self._file_number, control):
|
if self._tox.file_control(self._friend_number, self._file_number, control):
|
||||||
self.set_state(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):
|
def _signal(self):
|
||||||
percentage = self._done / self._size if self._size else 0
|
percentage = self._done / self._size if self._size else 0
|
||||||
if self._creation_time is None or not percentage:
|
if self._creation_time is None or not percentage:
|
||||||
@ -126,9 +128,7 @@ class FileTransfer:
|
|||||||
def _finished(self):
|
def _finished(self):
|
||||||
self._finished_event(self._friend_number, self._file_number)
|
self._finished_event(self._friend_number, self._file_number)
|
||||||
|
|
||||||
# -----------------------------------------------------------------------------------------------------------------
|
|
||||||
# Send file
|
# Send file
|
||||||
# -----------------------------------------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
|
|
||||||
class SendTransfer(FileTransfer):
|
class SendTransfer(FileTransfer):
|
||||||
@ -174,11 +174,14 @@ class SendAvatar(SendTransfer):
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, path, tox, friend_number):
|
def __init__(self, path, tox, friend_number):
|
||||||
if path is None:
|
LOG_DEBUG(f"SendAvatar path={path} friend_number={friend_number}")
|
||||||
|
if path is None or not os.path.exists(path):
|
||||||
avatar_hash = None
|
avatar_hash = None
|
||||||
else:
|
else:
|
||||||
with open(path, 'rb') as fl:
|
with open(path, 'rb') as fl:
|
||||||
avatar_hash = Tox.hash(fl.read())
|
data=fl.read()
|
||||||
|
LOG_DEBUG(f"SendAvatar data={data} type={type(data)}")
|
||||||
|
avatar_hash = tox.hash(data, None)
|
||||||
super().__init__(path, tox, friend_number, TOX_FILE_KIND['AVATAR'], avatar_hash)
|
super().__init__(path, tox, friend_number, TOX_FILE_KIND['AVATAR'], avatar_hash)
|
||||||
|
|
||||||
|
|
||||||
@ -220,12 +223,10 @@ class SendFromFileBuffer(SendTransfer):
|
|||||||
def send_chunk(self, position, size):
|
def send_chunk(self, position, size):
|
||||||
super().send_chunk(position, size)
|
super().send_chunk(position, size)
|
||||||
if not size:
|
if not size:
|
||||||
chdir(dirname(self._path))
|
os.chdir(dirname(self._path))
|
||||||
remove(self._path)
|
os.remove(self._path)
|
||||||
|
|
||||||
# -----------------------------------------------------------------------------------------------------------------
|
|
||||||
# Receive file
|
# Receive file
|
||||||
# -----------------------------------------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
|
|
||||||
class ReceiveTransfer(FileTransfer):
|
class ReceiveTransfer(FileTransfer):
|
||||||
@ -315,7 +316,6 @@ class ReceiveAvatar(ReceiveTransfer):
|
|||||||
Get friend's avatar. Doesn't need file transfer item
|
Get friend's avatar. Doesn't need file transfer item
|
||||||
"""
|
"""
|
||||||
MAX_AVATAR_SIZE = 512 * 1024
|
MAX_AVATAR_SIZE = 512 * 1024
|
||||||
|
|
||||||
def __init__(self, path, tox, friend_number, size, file_number):
|
def __init__(self, path, tox, friend_number, size, file_number):
|
||||||
full_path = path + '.tmp'
|
full_path = path + '.tmp'
|
||||||
super().__init__(full_path, tox, friend_number, size, file_number)
|
super().__init__(full_path, tox, friend_number, size, file_number)
|
||||||
@ -328,11 +328,11 @@ class ReceiveAvatar(ReceiveTransfer):
|
|||||||
self._file.close()
|
self._file.close()
|
||||||
remove(full_path)
|
remove(full_path)
|
||||||
elif exists(path):
|
elif exists(path):
|
||||||
hash = self.get_file_id()
|
ihash = self.get_file_id()
|
||||||
with open(path, 'rb') as fl:
|
with open(path, 'rb') as fl:
|
||||||
data = fl.read()
|
data = fl.read()
|
||||||
existing_hash = Tox.hash(data)
|
existing_hash = Tox.hash(data)
|
||||||
if hash == existing_hash:
|
if ihash == existing_hash:
|
||||||
self.send_control(TOX_FILE_CONTROL['CANCEL'])
|
self.send_control(TOX_FILE_CONTROL['CANCEL'])
|
||||||
self._file.close()
|
self._file.close()
|
||||||
remove(full_path)
|
remove(full_path)
|
||||||
|
@ -1,14 +1,17 @@
|
|||||||
# -*- mode: python; indent-tabs-mode: nil; py-indent-offset: 4; coding: utf-8 -*-
|
# -*- mode: python; indent-tabs-mode: nil; py-indent-offset: 4; coding: utf-8 -*-
|
||||||
|
|
||||||
|
import logging
|
||||||
|
|
||||||
from messenger.messages import *
|
from messenger.messages import *
|
||||||
|
from file_transfers.file_transfers import SendAvatar, is_inline
|
||||||
from ui.contact_items import *
|
from ui.contact_items import *
|
||||||
import utils.util as util
|
import utils.util as util
|
||||||
from common.tox_save import ToxSave
|
from common.tox_save import ToxSave
|
||||||
from wrapper_tests.support_testing import assert_main_thread
|
|
||||||
from copy import deepcopy
|
from middleware.callbacks import LOG_ERROR, LOG_WARN, LOG_INFO, LOG_DEBUG, LOG_TRACE
|
||||||
|
|
||||||
# LOG=util.log
|
# LOG=util.log
|
||||||
global LOG
|
global LOG
|
||||||
import logging
|
|
||||||
LOG = logging.getLogger('app.'+__name__)
|
LOG = logging.getLogger('app.'+__name__)
|
||||||
log = lambda x: LOG.info(x)
|
log = lambda x: LOG.info(x)
|
||||||
|
|
||||||
@ -29,15 +32,14 @@ class FileTransfersHandler(ToxSave):
|
|||||||
profile.avatar_changed_event.add_callback(self._send_avatar_to_contacts)
|
profile.avatar_changed_event.add_callback(self._send_avatar_to_contacts)
|
||||||
self. lBlockAvatars = []
|
self. lBlockAvatars = []
|
||||||
|
|
||||||
def stop(self):
|
def stop(self) -> None:
|
||||||
self._settings['paused_file_transfers'] = self._paused_file_transfers if self._settings['resend_files'] else {}
|
self._settings['paused_file_transfers'] = self._paused_file_transfers if self._settings['resend_files'] else {}
|
||||||
self._settings.save()
|
self._settings.save()
|
||||||
|
|
||||||
# -----------------------------------------------------------------------------------------------------------------
|
|
||||||
# File transfers support
|
# File transfers support
|
||||||
# -----------------------------------------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
def incoming_file_transfer(self, friend_number, file_number, size, file_name):
|
def incoming_file_transfer(self, friend_number, file_number, size, file_name) -> None:
|
||||||
|
# main thread
|
||||||
"""
|
"""
|
||||||
New transfer
|
New transfer
|
||||||
:param friend_number: number of friend who sent file
|
:param friend_number: number of friend who sent file
|
||||||
@ -46,12 +48,15 @@ class FileTransfersHandler(ToxSave):
|
|||||||
:param file_name: file name without path
|
:param file_name: file name without path
|
||||||
"""
|
"""
|
||||||
friend = self._get_friend_by_number(friend_number)
|
friend = self._get_friend_by_number(friend_number)
|
||||||
if friend is None: return None
|
if friend is None:
|
||||||
|
LOG.info(f'incoming_file_handler Friend NULL friend_number={friend_number}')
|
||||||
|
return
|
||||||
auto = self._settings['allow_auto_accept'] and friend.tox_id in self._settings['auto_accept_from_friends']
|
auto = self._settings['allow_auto_accept'] and friend.tox_id in self._settings['auto_accept_from_friends']
|
||||||
inline = is_inline(file_name) and self._settings['allow_inline']
|
inline = False # ?is_inline(file_name) and self._settings['allow_inline']
|
||||||
file_id = self._tox.file_get_file_id(friend_number, file_number)
|
file_id = self._tox.file_get_file_id(friend_number, file_number)
|
||||||
accepted = True
|
accepted = True
|
||||||
if file_id in self._paused_file_transfers:
|
if file_id in self._paused_file_transfers:
|
||||||
|
LOG_INFO(f'incoming_file_handler paused friend_number={friend_number}')
|
||||||
(path, ft_friend_number, is_incoming, start_position) = self._paused_file_transfers[file_id]
|
(path, ft_friend_number, is_incoming, start_position) = self._paused_file_transfers[file_id]
|
||||||
pos = start_position if os.path.exists(path) else 0
|
pos = start_position if os.path.exists(path) else 0
|
||||||
if pos >= size:
|
if pos >= size:
|
||||||
@ -62,26 +67,33 @@ class FileTransfersHandler(ToxSave):
|
|||||||
friend, accepted, size, file_name, file_number)
|
friend, accepted, size, file_name, file_number)
|
||||||
self.accept_transfer(path, friend_number, file_number, size, False, pos)
|
self.accept_transfer(path, friend_number, file_number, size, False, pos)
|
||||||
elif inline and size < 1024 * 1024:
|
elif inline and size < 1024 * 1024:
|
||||||
|
LOG_INFO(f'incoming_file_handler small friend_number={friend_number}')
|
||||||
self._file_transfers_message_service.add_incoming_transfer_message(
|
self._file_transfers_message_service.add_incoming_transfer_message(
|
||||||
friend, accepted, size, file_name, file_number)
|
friend, accepted, size, file_name, file_number)
|
||||||
self.accept_transfer('', friend_number, file_number, size, True)
|
self.accept_transfer('', friend_number, file_number, size, True)
|
||||||
elif auto:
|
elif auto:
|
||||||
|
# accepted is really started
|
||||||
|
LOG_INFO(f'incoming_file_handler auto friend_number={friend_number}')
|
||||||
path = self._settings['auto_accept_path'] or util.curr_directory()
|
path = self._settings['auto_accept_path'] or util.curr_directory()
|
||||||
self._file_transfers_message_service.add_incoming_transfer_message(
|
self._file_transfers_message_service.add_incoming_transfer_message(
|
||||||
friend, accepted, size, file_name, file_number)
|
friend, accepted, size, file_name, file_number)
|
||||||
self.accept_transfer(path + '/' + file_name, friend_number, file_number, size)
|
self.accept_transfer(path + '/' + file_name, friend_number, file_number, size)
|
||||||
else:
|
else:
|
||||||
|
LOG_INFO(f'incoming_file_handler reject friend_number={friend_number}')
|
||||||
accepted = False
|
accepted = False
|
||||||
|
# FixME: need GUI ask
|
||||||
|
# accepted is really started
|
||||||
self._file_transfers_message_service.add_incoming_transfer_message(
|
self._file_transfers_message_service.add_incoming_transfer_message(
|
||||||
friend, accepted, size, file_name, file_number)
|
friend, accepted, size, file_name, file_number)
|
||||||
|
|
||||||
def cancel_transfer(self, friend_number, file_number, already_cancelled=False):
|
def cancel_transfer(self, friend_number, file_number, already_cancelled=False) -> None:
|
||||||
"""
|
"""
|
||||||
Stop transfer
|
Stop transfer
|
||||||
:param friend_number: number of friend
|
:param friend_number: number of friend
|
||||||
:param file_number: file number
|
:param file_number: file number
|
||||||
:param already_cancelled: was cancelled by friend
|
:param already_cancelled: was cancelled by friend
|
||||||
"""
|
"""
|
||||||
|
# callback
|
||||||
if (friend_number, file_number) in self._file_transfers:
|
if (friend_number, file_number) in self._file_transfers:
|
||||||
tr = self._file_transfers[(friend_number, file_number)]
|
tr = self._file_transfers[(friend_number, file_number)]
|
||||||
if not already_cancelled:
|
if not already_cancelled:
|
||||||
@ -94,19 +106,19 @@ class FileTransfersHandler(ToxSave):
|
|||||||
elif not already_cancelled:
|
elif not already_cancelled:
|
||||||
self._tox.file_control(friend_number, file_number, TOX_FILE_CONTROL['CANCEL'])
|
self._tox.file_control(friend_number, file_number, TOX_FILE_CONTROL['CANCEL'])
|
||||||
|
|
||||||
def cancel_not_started_transfer(self, friend_number, message_id):
|
def cancel_not_started_transfer(self, friend_number, message_id) -> None:
|
||||||
friend = self._get_friend_by_number(friend_number)
|
friend = self._get_friend_by_number(friend_number)
|
||||||
if friend is None: return None
|
if friend is None: return None
|
||||||
friend.delete_one_unsent_file(message_id)
|
friend.delete_one_unsent_file(message_id)
|
||||||
|
|
||||||
def pause_transfer(self, friend_number, file_number, by_friend=False):
|
def pause_transfer(self, friend_number, file_number, by_friend=False) -> None:
|
||||||
"""
|
"""
|
||||||
Pause transfer with specified data
|
Pause transfer with specified data
|
||||||
"""
|
"""
|
||||||
tr = self._file_transfers[(friend_number, file_number)]
|
tr = self._file_transfers[(friend_number, file_number)]
|
||||||
tr.pause(by_friend)
|
tr.pause(by_friend)
|
||||||
|
|
||||||
def resume_transfer(self, friend_number, file_number, by_friend=False):
|
def resume_transfer(self, friend_number, file_number, by_friend=False) -> None:
|
||||||
"""
|
"""
|
||||||
Resume transfer with specified data
|
Resume transfer with specified data
|
||||||
"""
|
"""
|
||||||
@ -116,7 +128,7 @@ class FileTransfersHandler(ToxSave):
|
|||||||
else:
|
else:
|
||||||
tr.send_control(TOX_FILE_CONTROL['RESUME'])
|
tr.send_control(TOX_FILE_CONTROL['RESUME'])
|
||||||
|
|
||||||
def accept_transfer(self, path, friend_number, file_number, size, inline=False, from_position=0):
|
def accept_transfer(self, path, friend_number, file_number, size, inline=False, from_position=0) -> None:
|
||||||
"""
|
"""
|
||||||
:param path: path for saving
|
:param path: path for saving
|
||||||
:param friend_number: friend number
|
:param friend_number: friend number
|
||||||
@ -143,7 +155,7 @@ class FileTransfersHandler(ToxSave):
|
|||||||
if inline:
|
if inline:
|
||||||
self._insert_inline_before[(friend_number, file_number)] = message.message_id
|
self._insert_inline_before[(friend_number, file_number)] = message.message_id
|
||||||
|
|
||||||
def send_screenshot(self, data, friend_number):
|
def send_screenshot(self, data, friend_number) -> None:
|
||||||
"""
|
"""
|
||||||
Send screenshot
|
Send screenshot
|
||||||
:param data: raw data - png format
|
:param data: raw data - png format
|
||||||
@ -151,23 +163,26 @@ class FileTransfersHandler(ToxSave):
|
|||||||
"""
|
"""
|
||||||
self.send_inline(data, 'toxygen_inline.png', friend_number)
|
self.send_inline(data, 'toxygen_inline.png', friend_number)
|
||||||
|
|
||||||
def send_sticker(self, path, friend_number):
|
def send_sticker(self, path, friend_number) -> None:
|
||||||
with open(path, 'rb') as fl:
|
with open(path, 'rb') as fl:
|
||||||
data = fl.read()
|
data = fl.read()
|
||||||
self.send_inline(data, 'sticker.png', friend_number)
|
self.send_inline(data, 'sticker.png', friend_number)
|
||||||
|
|
||||||
def send_inline(self, data, file_name, friend_number, is_resend=False):
|
def send_inline(self, data, file_name, friend_number, is_resend=False) -> None:
|
||||||
friend = self._get_friend_by_number(friend_number)
|
friend = self._get_friend_by_number(friend_number)
|
||||||
if friend is None: return None
|
if friend is None:
|
||||||
|
LOG_WARN("fsend_inline Error friend is None file_name: {file_name}")
|
||||||
|
return
|
||||||
if friend.status is None and not is_resend:
|
if friend.status is None and not is_resend:
|
||||||
self._file_transfers_message_service.add_unsent_file_message(friend, file_name, data)
|
self._file_transfers_message_service.add_unsent_file_message(friend, file_name, data)
|
||||||
return
|
return
|
||||||
elif friend.status is None and is_resend:
|
elif friend.status is None and is_resend:
|
||||||
raise RuntimeError()
|
LOG_WARN("fsend_inline Error friend.status is None file_name: {file_name}")
|
||||||
|
return
|
||||||
st = SendFromBuffer(self._tox, friend.number, data, file_name)
|
st = SendFromBuffer(self._tox, friend.number, data, file_name)
|
||||||
self._send_file_add_set_handlers(st, friend, file_name, True)
|
self._send_file_add_set_handlers(st, friend, file_name, True)
|
||||||
|
|
||||||
def send_file(self, path, friend_number, is_resend=False, file_id=None):
|
def send_file(self, path, friend_number, is_resend=False, file_id=None) -> None:
|
||||||
"""
|
"""
|
||||||
Send file to current active friend
|
Send file to current active friend
|
||||||
:param path: file path
|
:param path: file path
|
||||||
@ -181,25 +196,25 @@ class FileTransfersHandler(ToxSave):
|
|||||||
self._file_transfers_message_service.add_unsent_file_message(friend, path, None)
|
self._file_transfers_message_service.add_unsent_file_message(friend, path, None)
|
||||||
return
|
return
|
||||||
elif friend.status is None and is_resend:
|
elif friend.status is None and is_resend:
|
||||||
LOG.error('Error in sending')
|
LOG_WARN('Error in sending')
|
||||||
return
|
return
|
||||||
st = SendTransfer(path, self._tox, friend_number, TOX_FILE_KIND['DATA'], file_id)
|
st = SendTransfer(path, self._tox, friend_number, TOX_FILE_KIND['DATA'], file_id)
|
||||||
file_name = os.path.basename(path)
|
file_name = os.path.basename(path)
|
||||||
self._send_file_add_set_handlers(st, friend, file_name)
|
self._send_file_add_set_handlers(st, friend, file_name)
|
||||||
|
|
||||||
def incoming_chunk(self, friend_number, file_number, position, data):
|
def incoming_chunk(self, friend_number, file_number, position, data) -> None:
|
||||||
"""
|
"""
|
||||||
Incoming chunk
|
Incoming chunk
|
||||||
"""
|
"""
|
||||||
self._file_transfers[(friend_number, file_number)].write_chunk(position, data)
|
self._file_transfers[(friend_number, file_number)].write_chunk(position, data)
|
||||||
|
|
||||||
def outgoing_chunk(self, friend_number, file_number, position, size):
|
def outgoing_chunk(self, friend_number, file_number, position, size) -> None:
|
||||||
"""
|
"""
|
||||||
Outgoing chunk
|
Outgoing chunk
|
||||||
"""
|
"""
|
||||||
self._file_transfers[(friend_number, file_number)].send_chunk(position, size)
|
self._file_transfers[(friend_number, file_number)].send_chunk(position, size)
|
||||||
|
|
||||||
def transfer_finished(self, friend_number, file_number):
|
def transfer_finished(self, friend_number, file_number) -> None:
|
||||||
transfer = self._file_transfers[(friend_number, file_number)]
|
transfer = self._file_transfers[(friend_number, file_number)]
|
||||||
friend = self._get_friend_by_number(friend_number)
|
friend = self._get_friend_by_number(friend_number)
|
||||||
if friend is None: return None
|
if friend is None: return None
|
||||||
@ -216,10 +231,10 @@ class FileTransfersHandler(ToxSave):
|
|||||||
self._file_transfers_message_service.add_inline_message(transfer, index)
|
self._file_transfers_message_service.add_inline_message(transfer, index)
|
||||||
del self._file_transfers[(friend_number, file_number)]
|
del self._file_transfers[(friend_number, file_number)]
|
||||||
|
|
||||||
def send_files(self, friend_number):
|
def send_files(self, friend_number:int) -> None:
|
||||||
try:
|
try:
|
||||||
friend = self._get_friend_by_number(friend_number)
|
friend = self._get_friend_by_number(friend_number)
|
||||||
if friend is None: return None
|
if friend is None: return
|
||||||
friend.remove_invalid_unsent_files()
|
friend.remove_invalid_unsent_files()
|
||||||
files = friend.get_unsent_files()
|
files = friend.get_unsent_files()
|
||||||
for fl in files:
|
for fl in files:
|
||||||
@ -238,9 +253,9 @@ class FileTransfersHandler(ToxSave):
|
|||||||
self.send_file(path, friend_number, True, key)
|
self.send_file(path, friend_number, True, key)
|
||||||
del self._paused_file_transfers[key]
|
del self._paused_file_transfers[key]
|
||||||
except Exception as ex:
|
except Exception as ex:
|
||||||
LOG.error('Exception in file sending: ' + str(ex))
|
LOG_ERROR('send_files EXCEPTION in file sending: ' + str(ex))
|
||||||
|
|
||||||
def friend_exit(self, friend_number):
|
def friend_exit(self, friend_number:int) -> None:
|
||||||
# RuntimeError: dictionary changed size during iteration
|
# RuntimeError: dictionary changed size during iteration
|
||||||
lMayChangeDynamically = self._file_transfers.copy()
|
lMayChangeDynamically = self._file_transfers.copy()
|
||||||
for friend_num, file_num in lMayChangeDynamically:
|
for friend_num, file_num in lMayChangeDynamically:
|
||||||
@ -250,32 +265,51 @@ class FileTransfersHandler(ToxSave):
|
|||||||
continue
|
continue
|
||||||
ft = self._file_transfers[(friend_num, file_num)]
|
ft = self._file_transfers[(friend_num, file_num)]
|
||||||
if type(ft) is SendTransfer:
|
if type(ft) is SendTransfer:
|
||||||
self._paused_file_transfers[ft.file_id] = [ft.path, friend_num, False, -1]
|
try:
|
||||||
|
file_id = ft.file_id
|
||||||
|
except Exception as e:
|
||||||
|
LOG_WARN("friend_exit SendTransfer Error getting file_id: {e}")
|
||||||
|
# drop through
|
||||||
|
else:
|
||||||
|
self._paused_file_transfers[file_id] = [ft.path, friend_num, False, -1]
|
||||||
elif type(ft) is ReceiveTransfer and ft.state != FILE_TRANSFER_STATE['INCOMING_NOT_STARTED']:
|
elif type(ft) is ReceiveTransfer and ft.state != FILE_TRANSFER_STATE['INCOMING_NOT_STARTED']:
|
||||||
self._paused_file_transfers[ft.file_id] = [ft.path, friend_num, True, ft.total_size()]
|
try:
|
||||||
|
file_id = ft.file_id
|
||||||
|
except Exception as e:
|
||||||
|
LOG_WARN("friend_exit ReceiveTransfer Error getting file_id: {e}")
|
||||||
|
# drop through
|
||||||
|
else:
|
||||||
|
self._paused_file_transfers[file_id] = [ft.path, friend_num, True, ft.total_size()]
|
||||||
self.cancel_transfer(friend_num, file_num, True)
|
self.cancel_transfer(friend_num, file_num, True)
|
||||||
|
|
||||||
# -----------------------------------------------------------------------------------------------------------------
|
|
||||||
# Avatars support
|
# Avatars support
|
||||||
# -----------------------------------------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
def send_avatar(self, friend_number, avatar_path=None):
|
def send_avatar(self, friend_number, avatar_path=None) -> None:
|
||||||
"""
|
"""
|
||||||
:param friend_number: number of friend who should get new avatar
|
:param friend_number: number of friend who should get new avatar
|
||||||
:param avatar_path: path to avatar or None if reset
|
:param avatar_path: path to avatar or None if reset
|
||||||
"""
|
"""
|
||||||
|
return
|
||||||
if (avatar_path, friend_number,) in self.lBlockAvatars:
|
if (avatar_path, friend_number,) in self.lBlockAvatars:
|
||||||
return
|
return
|
||||||
|
if friend_number is None:
|
||||||
|
LOG_WARN(f"send_avatar friend_number NULL {friend_number}")
|
||||||
|
return
|
||||||
|
if avatar_path and type(avatar_path) != str:
|
||||||
|
LOG_WARN(f"send_avatar avatar_path type {type(avatar_path)}")
|
||||||
|
return
|
||||||
|
LOG_INFO(f"send_avatar avatar_path={avatar_path} friend_number={friend_number}")
|
||||||
try:
|
try:
|
||||||
|
# self NOT missing - who's self?
|
||||||
sa = SendAvatar(avatar_path, self._tox, friend_number)
|
sa = SendAvatar(avatar_path, self._tox, friend_number)
|
||||||
|
LOG_INFO(f"send_avatar avatar_path={avatar_path} sa={sa}")
|
||||||
self._file_transfers[(friend_number, sa.file_number)] = sa
|
self._file_transfers[(friend_number, sa.file_number)] = sa
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
# ArgumentError('This client is currently not connected to the friend.')
|
# ArgumentError('This client is currently not connected to the friend.')
|
||||||
LOG.error(f"send_avatar {e}")
|
LOG_WARN(f"send_avatar EXCEPTION {e}")
|
||||||
self.lBlockAvatars.append( (avatar_path, friend_number,) )
|
self.lBlockAvatars.append( (avatar_path, friend_number,) )
|
||||||
|
|
||||||
def incoming_avatar(self, friend_number, file_number, size):
|
def incoming_avatar(self, friend_number, file_number, size) -> None:
|
||||||
"""
|
"""
|
||||||
Friend changed avatar
|
Friend changed avatar
|
||||||
:param friend_number: friend number
|
:param friend_number: friend number
|
||||||
@ -283,7 +317,7 @@ class FileTransfersHandler(ToxSave):
|
|||||||
:param size: size of avatar or 0 (default avatar)
|
:param size: size of avatar or 0 (default avatar)
|
||||||
"""
|
"""
|
||||||
friend = self._get_friend_by_number(friend_number)
|
friend = self._get_friend_by_number(friend_number)
|
||||||
if friend is None: return None
|
if friend is None: return
|
||||||
ra = ReceiveAvatar(friend.get_contact_avatar_path(), self._tox, friend_number, size, file_number)
|
ra = ReceiveAvatar(friend.get_contact_avatar_path(), self._tox, friend_number, size, file_number)
|
||||||
if ra.state != FILE_TRANSFER_STATE['CANCELLED']:
|
if ra.state != FILE_TRANSFER_STATE['CANCELLED']:
|
||||||
self._file_transfers[(friend_number, file_number)] = ra
|
self._file_transfers[(friend_number, file_number)] = ra
|
||||||
@ -291,23 +325,21 @@ class FileTransfersHandler(ToxSave):
|
|||||||
elif not size:
|
elif not size:
|
||||||
friend.reset_avatar(self._settings['identicons'])
|
friend.reset_avatar(self._settings['identicons'])
|
||||||
|
|
||||||
def _send_avatar_to_contacts(self, _):
|
def _send_avatar_to_contacts(self, _) -> None:
|
||||||
# from a callback
|
# from a callback
|
||||||
friends = self._get_all_friends()
|
friends = self._get_all_friends()
|
||||||
for friend in filter(self._is_friend_online, friends):
|
for friend in filter(self._is_friend_online, friends):
|
||||||
self.send_avatar(friend.number)
|
self.send_avatar(friend.number)
|
||||||
|
|
||||||
# -----------------------------------------------------------------------------------------------------------------
|
|
||||||
# Private methods
|
# Private methods
|
||||||
# -----------------------------------------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
def _is_friend_online(self, friend_number):
|
def _is_friend_online(self, friend_number:int) -> bool:
|
||||||
friend = self._get_friend_by_number(friend_number)
|
friend = self._get_friend_by_number(friend_number)
|
||||||
if friend is None: return None
|
if friend is None: return None
|
||||||
|
|
||||||
return friend.status is not None
|
return friend.status is not None
|
||||||
|
|
||||||
def _get_friend_by_number(self, friend_number):
|
def _get_friend_by_number(self, friend_number:int):
|
||||||
return self._contact_provider.get_friend_by_number(friend_number)
|
return self._contact_provider.get_friend_by_number(friend_number)
|
||||||
|
|
||||||
def _get_all_friends(self):
|
def _get_all_friends(self):
|
||||||
|
@ -1,16 +1,15 @@
|
|||||||
|
# -*- mode: python; indent-tabs-mode: nil; py-indent-offset: 4; coding: utf-8 -*-
|
||||||
|
|
||||||
|
import logging
|
||||||
|
|
||||||
from messenger.messenger import *
|
from messenger.messenger import *
|
||||||
import utils.util as util
|
import utils.util as util
|
||||||
from file_transfers.file_transfers import *
|
from file_transfers.file_transfers import *
|
||||||
|
|
||||||
global LOG
|
global LOG
|
||||||
import logging
|
|
||||||
LOG = logging.getLogger('app.'+__name__)
|
LOG = logging.getLogger('app.'+__name__)
|
||||||
|
|
||||||
def LOG_ERROR(l): print('ERROR_: '+l)
|
from av.calls import LOG_ERROR, LOG_WARN, LOG_INFO, LOG_DEBUG, LOG_TRACE
|
||||||
def LOG_WARN(l): print('WARN_: '+l)
|
|
||||||
def LOG_INFO(l): print('INFO_: '+l)
|
|
||||||
def LOG_DEBUG(l): print('DEBUG_: '+l)
|
|
||||||
def LOG_TRACE(l): pass # print('TRACE+ '+l)
|
|
||||||
|
|
||||||
class FileTransfersMessagesService:
|
class FileTransfersMessagesService:
|
||||||
|
|
||||||
@ -23,6 +22,7 @@ class FileTransfersMessagesService:
|
|||||||
def add_incoming_transfer_message(self, friend, accepted, size, file_name, file_number):
|
def add_incoming_transfer_message(self, friend, accepted, size, file_name, file_number):
|
||||||
assert friend
|
assert friend
|
||||||
author = MessageAuthor(friend.name, MESSAGE_AUTHOR['FRIEND'])
|
author = MessageAuthor(friend.name, MESSAGE_AUTHOR['FRIEND'])
|
||||||
|
# accepted is really started
|
||||||
status = FILE_TRANSFER_STATE['RUNNING'] if accepted else FILE_TRANSFER_STATE['INCOMING_NOT_STARTED']
|
status = FILE_TRANSFER_STATE['RUNNING'] if accepted else FILE_TRANSFER_STATE['INCOMING_NOT_STARTED']
|
||||||
tm = TransferMessage(author, util.get_unix_time(), status, size, file_name, friend.number, file_number)
|
tm = TransferMessage(author, util.get_unix_time(), status, size, file_name, friend.number, file_number)
|
||||||
|
|
||||||
@ -50,7 +50,7 @@ class FileTransfersMessagesService:
|
|||||||
|
|
||||||
return tm
|
return tm
|
||||||
|
|
||||||
def add_inline_message(self, transfer, index):
|
def add_inline_message(self, transfer, index) -> None:
|
||||||
"""callback"""
|
"""callback"""
|
||||||
if not self._is_friend_active(transfer.friend_number):
|
if not self._is_friend_active(transfer.friend_number):
|
||||||
return
|
return
|
||||||
@ -60,7 +60,8 @@ class FileTransfersMessagesService:
|
|||||||
return
|
return
|
||||||
count = self._messages.count()
|
count = self._messages.count()
|
||||||
if count + index + 1 >= 0:
|
if count + index + 1 >= 0:
|
||||||
self._create_inline_item(transfer.data, count + index + 1)
|
# assumes .data
|
||||||
|
self._create_inline_item(transfer, count + index + 1)
|
||||||
|
|
||||||
def add_unsent_file_message(self, friend, file_path, data):
|
def add_unsent_file_message(self, friend, file_path, data):
|
||||||
assert friend
|
assert friend
|
||||||
@ -75,11 +76,9 @@ class FileTransfersMessagesService:
|
|||||||
|
|
||||||
return tm
|
return tm
|
||||||
|
|
||||||
# -----------------------------------------------------------------------------------------------------------------
|
|
||||||
# Private methods
|
# Private methods
|
||||||
# -----------------------------------------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
def _is_friend_active(self, friend_number):
|
def _is_friend_active(self, friend_number:int) -> bool:
|
||||||
if not self._contacts_manager.is_active_a_friend():
|
if not self._contacts_manager.is_active_a_friend():
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
|
# -*- mode: python; indent-tabs-mode: nil; py-indent-offset: 4; coding: utf-8 -*-
|
||||||
|
|
||||||
class GroupBan:
|
class GroupBan:
|
||||||
|
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
|
# -*- mode: python; indent-tabs-mode: nil; py-indent-offset: 4; coding: utf-8 -*-
|
||||||
|
|
||||||
class GroupInvite:
|
class GroupInvite:
|
||||||
|
|
||||||
|
@ -1,23 +1,22 @@
|
|||||||
|
# -*- mode: python; indent-tabs-mode: nil; py-indent-offset: 4; coding: utf-8 -*-
|
||||||
|
|
||||||
class GroupChatPeer:
|
class GroupChatPeer:
|
||||||
"""
|
"""
|
||||||
Represents peer in group chat.
|
Represents peer in group chat.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, peer_id, name, status, role, public_key, is_current_user=False, is_muted=False):
|
def __init__(self, peer_id, name, status, role, public_key, is_current_user=False, is_muted=False, status_message=None):
|
||||||
self._peer_id = peer_id
|
self._peer_id = peer_id
|
||||||
self._name = name
|
self._name = name
|
||||||
self._status = status
|
self._status = status
|
||||||
|
self._status_message = status_message
|
||||||
self._role = role
|
self._role = role
|
||||||
self._public_key = public_key
|
self._public_key = public_key
|
||||||
self._is_current_user = is_current_user
|
self._is_current_user = is_current_user
|
||||||
self._is_muted = is_muted
|
self._is_muted = is_muted
|
||||||
# unused?
|
|
||||||
self._kind = 'grouppeer'
|
self._kind = 'grouppeer'
|
||||||
# -----------------------------------------------------------------------------------------------------------------
|
|
||||||
# Readonly properties
|
# Readonly properties
|
||||||
# -----------------------------------------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
def get_id(self):
|
def get_id(self):
|
||||||
return self._peer_id
|
return self._peer_id
|
||||||
@ -34,9 +33,12 @@ class GroupChatPeer:
|
|||||||
|
|
||||||
is_current_user = property(get_is_current_user)
|
is_current_user = property(get_is_current_user)
|
||||||
|
|
||||||
# -----------------------------------------------------------------------------------------------------------------
|
def get_status_message(self):
|
||||||
|
return self._status_message
|
||||||
|
|
||||||
|
status_message = property(get_status_message)
|
||||||
|
|
||||||
# Read-write properties
|
# Read-write properties
|
||||||
# -----------------------------------------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
def get_name(self):
|
def get_name(self):
|
||||||
return self._name
|
return self._name
|
||||||
|
@ -1,15 +1,15 @@
|
|||||||
# -*- mode: python; indent-tabs-mode: nil; py-indent-offset: 4; coding: utf-8 -*-
|
# -*- mode: python; indent-tabs-mode: nil; py-indent-offset: 4; coding: utf-8 -*-
|
||||||
|
import logging
|
||||||
|
|
||||||
import common.tox_save as tox_save
|
import common.tox_save as tox_save
|
||||||
import utils.ui as util_ui
|
import utils.ui as util_ui
|
||||||
from groups.peers_list import PeersListGenerator
|
from groups.peers_list import PeersListGenerator
|
||||||
from groups.group_invite import GroupInvite
|
from groups.group_invite import GroupInvite
|
||||||
import wrapper.toxcore_enums_and_consts as constants
|
import toxygen_wrapper.toxcore_enums_and_consts as constants
|
||||||
from wrapper.toxcore_enums_and_consts import *
|
from toxygen_wrapper.toxcore_enums_and_consts import *
|
||||||
from wrapper.tox import UINT32_MAX
|
from toxygen_wrapper.tox import UINT32_MAX
|
||||||
|
|
||||||
global LOG
|
global LOG
|
||||||
import logging
|
|
||||||
LOG = logging.getLogger('app.'+'gs')
|
LOG = logging.getLogger('app.'+'gs')
|
||||||
|
|
||||||
class GroupsService(tox_save.ToxSave):
|
class GroupsService(tox_save.ToxSave):
|
||||||
@ -26,16 +26,14 @@ class GroupsService(tox_save.ToxSave):
|
|||||||
# maybe just use self
|
# maybe just use self
|
||||||
self._tox = tox
|
self._tox = tox
|
||||||
|
|
||||||
def set_tox(self, tox):
|
def set_tox(self, tox) -> None:
|
||||||
super().set_tox(tox)
|
super().set_tox(tox)
|
||||||
for group in self._get_all_groups():
|
for group in self._get_all_groups():
|
||||||
group.set_tox(tox)
|
group.set_tox(tox)
|
||||||
|
|
||||||
# -----------------------------------------------------------------------------------------------------------------
|
|
||||||
# Groups creation
|
# Groups creation
|
||||||
# -----------------------------------------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
def create_new_gc(self, name, privacy_state, nick, status):
|
def create_new_gc(self, name, privacy_state, nick, status) -> None:
|
||||||
try:
|
try:
|
||||||
group_number = self._tox.group_new(privacy_state, name, nick, status)
|
group_number = self._tox.group_new(privacy_state, name, nick, status)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
@ -49,7 +47,7 @@ class GroupsService(tox_save.ToxSave):
|
|||||||
group.status = constants.TOX_USER_STATUS['NONE']
|
group.status = constants.TOX_USER_STATUS['NONE']
|
||||||
self._contacts_manager.update_filtration()
|
self._contacts_manager.update_filtration()
|
||||||
|
|
||||||
def join_gc_by_id(self, chat_id, password, nick, status):
|
def join_gc_by_id(self, chat_id, password, nick, status) -> None:
|
||||||
try:
|
try:
|
||||||
group_number = self._tox.group_join(chat_id, password, nick, status)
|
group_number = self._tox.group_join(chat_id, password, nick, status)
|
||||||
assert type(group_number) == int, group_number
|
assert type(group_number) == int, group_number
|
||||||
@ -74,32 +72,28 @@ class GroupsService(tox_save.ToxSave):
|
|||||||
group.status = constants.TOX_USER_STATUS['NONE']
|
group.status = constants.TOX_USER_STATUS['NONE']
|
||||||
self._contacts_manager.update_filtration()
|
self._contacts_manager.update_filtration()
|
||||||
|
|
||||||
# -----------------------------------------------------------------------------------------------------------------
|
|
||||||
# Groups reconnect and leaving
|
# Groups reconnect and leaving
|
||||||
# -----------------------------------------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
def leave_group(self, group_number):
|
def leave_group(self, group_number) -> None:
|
||||||
if type(group_number) == int:
|
if type(group_number) == int:
|
||||||
self._tox.group_leave(group_number)
|
self._tox.group_leave(group_number)
|
||||||
self._contacts_manager.delete_group(group_number)
|
self._contacts_manager.delete_group(group_number)
|
||||||
|
|
||||||
def disconnect_from_group(self, group_number):
|
def disconnect_from_group(self, group_number) -> None:
|
||||||
self._tox.group_disconnect(group_number)
|
self._tox.group_disconnect(group_number)
|
||||||
group = self._get_group_by_number(group_number)
|
group = self._get_group_by_number(group_number)
|
||||||
group.status = None
|
group.status = None
|
||||||
self._clear_peers_list(group)
|
self._clear_peers_list(group)
|
||||||
|
|
||||||
def reconnect_to_group(self, group_number):
|
def reconnect_to_group(self, group_number) -> None:
|
||||||
self._tox.group_reconnect(group_number)
|
self._tox.group_reconnect(group_number)
|
||||||
group = self._get_group_by_number(group_number)
|
group = self._get_group_by_number(group_number)
|
||||||
group.status = constants.TOX_USER_STATUS['NONE']
|
group.status = constants.TOX_USER_STATUS['NONE']
|
||||||
self._clear_peers_list(group)
|
self._clear_peers_list(group)
|
||||||
|
|
||||||
# -----------------------------------------------------------------------------------------------------------------
|
|
||||||
# Group invites
|
# Group invites
|
||||||
# -----------------------------------------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
def invite_friend(self, friend_number, group_number):
|
def invite_friend(self, friend_number, group_number) -> None:
|
||||||
if self._tox.friend_get_connection_status(friend_number) == TOX_CONNECTION['NONE']:
|
if self._tox.friend_get_connection_status(friend_number) == TOX_CONNECTION['NONE']:
|
||||||
title = f"Error in group_invite_friend {friend_number}"
|
title = f"Error in group_invite_friend {friend_number}"
|
||||||
e = f"Friend not connected friend_number={friend_number}"
|
e = f"Friend not connected friend_number={friend_number}"
|
||||||
@ -112,7 +106,7 @@ class GroupsService(tox_save.ToxSave):
|
|||||||
title = f"Error in group_invite_friend {group_number} {friend_number}"
|
title = f"Error in group_invite_friend {group_number} {friend_number}"
|
||||||
util_ui.message_box(title +'\n' +str(e), title)
|
util_ui.message_box(title +'\n' +str(e), title)
|
||||||
|
|
||||||
def process_group_invite(self, friend_number, group_name, invite_data):
|
def process_group_invite(self, friend_number, group_name, invite_data) -> None:
|
||||||
friend = self._get_friend_by_number(friend_number)
|
friend = self._get_friend_by_number(friend_number)
|
||||||
# binary {invite_data}
|
# binary {invite_data}
|
||||||
LOG.debug(f"process_group_invite {friend_number} {group_name}")
|
LOG.debug(f"process_group_invite {friend_number} {group_name}")
|
||||||
@ -120,7 +114,7 @@ class GroupsService(tox_save.ToxSave):
|
|||||||
self._group_invites.append(invite)
|
self._group_invites.append(invite)
|
||||||
self._update_invites_button_state()
|
self._update_invites_button_state()
|
||||||
|
|
||||||
def accept_group_invite(self, invite, name, status, password):
|
def accept_group_invite(self, invite, name, status, password) -> None:
|
||||||
pk = invite.friend_public_key
|
pk = invite.friend_public_key
|
||||||
friend = self._get_friend_by_public_key(pk)
|
friend = self._get_friend_by_public_key(pk)
|
||||||
LOG.debug(f"accept_group_invite {name}")
|
LOG.debug(f"accept_group_invite {name}")
|
||||||
@ -128,7 +122,7 @@ class GroupsService(tox_save.ToxSave):
|
|||||||
self._delete_group_invite(invite)
|
self._delete_group_invite(invite)
|
||||||
self._update_invites_button_state()
|
self._update_invites_button_state()
|
||||||
|
|
||||||
def decline_group_invite(self, invite):
|
def decline_group_invite(self, invite) -> None:
|
||||||
self._delete_group_invite(invite)
|
self._delete_group_invite(invite)
|
||||||
self._main_screen.update_gc_invites_button_state()
|
self._main_screen.update_gc_invites_button_state()
|
||||||
|
|
||||||
@ -142,15 +136,13 @@ class GroupsService(tox_save.ToxSave):
|
|||||||
|
|
||||||
group_invites_count = property(get_group_invites_count)
|
group_invites_count = property(get_group_invites_count)
|
||||||
|
|
||||||
# -----------------------------------------------------------------------------------------------------------------
|
|
||||||
# Group info methods
|
# Group info methods
|
||||||
# -----------------------------------------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
def update_group_info(self, group):
|
def update_group_info(self, group):
|
||||||
group.name = self._tox.group_get_name(group.number)
|
group.name = self._tox.group_get_name(group.number)
|
||||||
group.status_message = self._tox.group_get_topic(group.number)
|
group.status_message = self._tox.group_get_topic(group.number)
|
||||||
|
|
||||||
def set_group_topic(self, group):
|
def set_group_topic(self, group) -> None:
|
||||||
if not group.is_self_moderator_or_founder():
|
if not group.is_self_moderator_or_founder():
|
||||||
return
|
return
|
||||||
text = util_ui.tr('New topic for group "{}":'.format(group.name))
|
text = util_ui.tr('New topic for group "{}":'.format(group.name))
|
||||||
@ -161,46 +153,44 @@ class GroupsService(tox_save.ToxSave):
|
|||||||
self._tox.group_set_topic(group.number, topic)
|
self._tox.group_set_topic(group.number, topic)
|
||||||
group.status_message = topic
|
group.status_message = topic
|
||||||
|
|
||||||
def show_group_management_screen(self, group):
|
def show_group_management_screen(self, group) -> None:
|
||||||
widgets_factory = self._get_widgets_factory()
|
widgets_factory = self._get_widgets_factory()
|
||||||
self._screen = widgets_factory.create_group_management_screen(group)
|
self._screen = widgets_factory.create_group_management_screen(group)
|
||||||
self._screen.show()
|
self._screen.show()
|
||||||
|
|
||||||
def show_group_settings_screen(self, group):
|
def show_group_settings_screen(self, group) -> None:
|
||||||
widgets_factory = self._get_widgets_factory()
|
widgets_factory = self._get_widgets_factory()
|
||||||
self._screen = widgets_factory.create_group_settings_screen(group)
|
self._screen = widgets_factory.create_group_settings_screen(group)
|
||||||
self._screen.show()
|
self._screen.show()
|
||||||
|
|
||||||
def set_group_password(self, group, password):
|
def set_group_password(self, group, password) -> None:
|
||||||
if group.password == password:
|
if group.password == password:
|
||||||
return
|
return
|
||||||
self._tox.group_founder_set_password(group.number, password)
|
self._tox.group_founder_set_password(group.number, password)
|
||||||
group.password = password
|
group.password = password
|
||||||
|
|
||||||
def set_group_peers_limit(self, group, peers_limit):
|
def set_group_peers_limit(self, group, peers_limit) -> None:
|
||||||
if group.peers_limit == peers_limit:
|
if group.peers_limit == peers_limit:
|
||||||
return
|
return
|
||||||
self._tox.group_founder_set_peer_limit(group.number, peers_limit)
|
self._tox.group_founder_set_peer_limit(group.number, peers_limit)
|
||||||
group.peers_limit = peers_limit
|
group.peers_limit = peers_limit
|
||||||
|
|
||||||
def set_group_privacy_state(self, group, privacy_state):
|
def set_group_privacy_state(self, group, privacy_state) -> None:
|
||||||
is_private = privacy_state == constants.TOX_GROUP_PRIVACY_STATE['PRIVATE']
|
is_private = privacy_state == constants.TOX_GROUP_PRIVACY_STATE['PRIVATE']
|
||||||
if group.is_private == is_private:
|
if group.is_private == is_private:
|
||||||
return
|
return
|
||||||
self._tox.group_founder_set_privacy_state(group.number, privacy_state)
|
self._tox.group_founder_set_privacy_state(group.number, privacy_state)
|
||||||
group.is_private = is_private
|
group.is_private = is_private
|
||||||
|
|
||||||
# -----------------------------------------------------------------------------------------------------------------
|
|
||||||
# Peers list
|
# Peers list
|
||||||
# -----------------------------------------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
def generate_peers_list(self):
|
def generate_peers_list(self) -> None:
|
||||||
if not self._contacts_manager.is_active_a_group():
|
if not self._contacts_manager.is_active_a_group():
|
||||||
return
|
return
|
||||||
group = self._contacts_manager.get_curr_contact()
|
group = self._contacts_manager.get_curr_contact()
|
||||||
PeersListGenerator().generate(group.peers, self, self._peers_list_widget, group.tox_id)
|
PeersListGenerator().generate(group.peers, self, self._peers_list_widget, group.tox_id)
|
||||||
|
|
||||||
def peer_selected(self, chat_id, peer_id):
|
def peer_selected(self, chat_id, peer_id) -> None:
|
||||||
widgets_factory = self._get_widgets_factory()
|
widgets_factory = self._get_widgets_factory()
|
||||||
group = self._get_group_by_public_key(chat_id)
|
group = self._get_group_by_public_key(chat_id)
|
||||||
self_peer = group.get_self_peer()
|
self_peer = group.get_self_peer()
|
||||||
@ -210,20 +200,18 @@ class GroupsService(tox_save.ToxSave):
|
|||||||
self._screen = widgets_factory.create_self_peer_screen_window(group)
|
self._screen = widgets_factory.create_self_peer_screen_window(group)
|
||||||
self._screen.show()
|
self._screen.show()
|
||||||
|
|
||||||
# -----------------------------------------------------------------------------------------------------------------
|
|
||||||
# Peers actions
|
# Peers actions
|
||||||
# -----------------------------------------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
def set_new_peer_role(self, group, peer, role):
|
def set_new_peer_role(self, group, peer, role) -> None:
|
||||||
self._tox.group_mod_set_role(group.number, peer.id, role)
|
self._tox.group_mod_set_role(group.number, peer.id, role)
|
||||||
peer.role = role
|
peer.role = role
|
||||||
self.generate_peers_list()
|
self.generate_peers_list()
|
||||||
|
|
||||||
def toggle_ignore_peer(self, group, peer, ignore):
|
def toggle_ignore_peer(self, group, peer, ignore) -> None:
|
||||||
self._tox.group_toggle_ignore(group.number, peer.id, ignore)
|
self._tox.group_toggle_ignore(group.number, peer.id, ignore)
|
||||||
peer.is_muted = ignore
|
peer.is_muted = ignore
|
||||||
|
|
||||||
def set_self_info(self, group, name, status):
|
def set_self_info(self, group, name, status) -> None:
|
||||||
self._tox.group_self_set_name(group.number, name)
|
self._tox.group_self_set_name(group.number, name)
|
||||||
self._tox.group_self_set_status(group.number, status)
|
self._tox.group_self_set_status(group.number, status)
|
||||||
self_peer = group.get_self_peer()
|
self_peer = group.get_self_peer()
|
||||||
@ -231,31 +219,27 @@ class GroupsService(tox_save.ToxSave):
|
|||||||
self_peer.status = status
|
self_peer.status = status
|
||||||
self.generate_peers_list()
|
self.generate_peers_list()
|
||||||
|
|
||||||
# -----------------------------------------------------------------------------------------------------------------
|
|
||||||
# Bans support
|
# Bans support
|
||||||
# -----------------------------------------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
def show_bans_list(self, group):
|
def show_bans_list(self, group) -> None:
|
||||||
return
|
return
|
||||||
widgets_factory = self._get_widgets_factory()
|
widgets_factory = self._get_widgets_factory()
|
||||||
self._screen = widgets_factory.create_groups_bans_screen(group)
|
self._screen = widgets_factory.create_groups_bans_screen(group)
|
||||||
self._screen.show()
|
self._screen.show()
|
||||||
|
|
||||||
def ban_peer(self, group, peer_id, ban_type):
|
def ban_peer(self, group, peer_id, ban_type) -> None:
|
||||||
self._tox.group_mod_ban_peer(group.number, peer_id, ban_type)
|
self._tox.group_mod_ban_peer(group.number, peer_id, ban_type)
|
||||||
|
|
||||||
def kick_peer(self, group, peer_id):
|
def kick_peer(self, group, peer_id) -> None:
|
||||||
self._tox.group_mod_remove_peer(group.number, peer_id)
|
self._tox.group_mod_remove_peer(group.number, peer_id)
|
||||||
|
|
||||||
def cancel_ban(self, group_number, ban_id):
|
def cancel_ban(self, group_number, ban_id) -> None:
|
||||||
self._tox.group_mod_remove_ban(group_number, ban_id)
|
self._tox.group_mod_remove_ban(group_number, ban_id)
|
||||||
|
|
||||||
# -----------------------------------------------------------------------------------------------------------------
|
|
||||||
# Private methods
|
# Private methods
|
||||||
# -----------------------------------------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
def _add_new_group_by_number(self, group_number):
|
def _add_new_group_by_number(self, group_number) -> None:
|
||||||
LOG.debug(f"_add_new_group_by_number {group_number}")
|
LOG.debug(f"_add_new_group_by_number group_number={group_number}")
|
||||||
self._contacts_manager.add_group(group_number)
|
self._contacts_manager.add_group(group_number)
|
||||||
|
|
||||||
def _get_group_by_number(self, group_number):
|
def _get_group_by_number(self, group_number):
|
||||||
@ -267,22 +251,22 @@ class GroupsService(tox_save.ToxSave):
|
|||||||
def _get_all_groups(self):
|
def _get_all_groups(self):
|
||||||
return self._contacts_provider.get_all_groups()
|
return self._contacts_provider.get_all_groups()
|
||||||
|
|
||||||
def _get_friend_by_number(self, friend_number):
|
def _get_friend_by_number(self, friend_number:int):
|
||||||
return self._contacts_provider.get_friend_by_number(friend_number)
|
return self._contacts_provider.get_friend_by_number(friend_number)
|
||||||
|
|
||||||
def _get_friend_by_public_key(self, public_key):
|
def _get_friend_by_public_key(self, public_key):
|
||||||
return self._contacts_provider.get_friend_by_public_key(public_key)
|
return self._contacts_provider.get_friend_by_public_key(public_key)
|
||||||
|
|
||||||
def _clear_peers_list(self, group):
|
def _clear_peers_list(self, group) -> None:
|
||||||
group.remove_all_peers_except_self()
|
group.remove_all_peers_except_self()
|
||||||
self.generate_peers_list()
|
self.generate_peers_list()
|
||||||
|
|
||||||
def _delete_group_invite(self, invite):
|
def _delete_group_invite(self, invite) -> None:
|
||||||
if invite in self._group_invites:
|
if invite in self._group_invites:
|
||||||
self._group_invites.remove(invite)
|
self._group_invites.remove(invite)
|
||||||
|
|
||||||
# status should be dropped
|
# status should be dropped
|
||||||
def _join_gc_via_invite(self, invite_data, friend_number, nick, status='', password=''):
|
def _join_gc_via_invite(self, invite_data, friend_number, nick, status='', password='') -> None:
|
||||||
LOG.debug(f"_join_gc_via_invite friend_number={friend_number} nick={nick} datalen={len(invite_data)}")
|
LOG.debug(f"_join_gc_via_invite friend_number={friend_number} nick={nick} datalen={len(invite_data)}")
|
||||||
if nick is None:
|
if nick is None:
|
||||||
nick = ''
|
nick = ''
|
||||||
@ -300,8 +284,8 @@ class GroupsService(tox_save.ToxSave):
|
|||||||
LOG.error(f"_join_gc_via_invite group_number={group_number} {e}")
|
LOG.error(f"_join_gc_via_invite group_number={group_number} {e}")
|
||||||
return
|
return
|
||||||
|
|
||||||
def _update_invites_button_state(self):
|
def _update_invites_button_state(self) -> None:
|
||||||
self._main_screen.update_gc_invites_button_state()
|
self._main_screen.update_gc_invites_button_state()
|
||||||
|
|
||||||
def _get_widgets_factory(self):
|
def _get_widgets_factory(self) -> None:
|
||||||
return self._widgets_factory_provider.get_item()
|
return self._widgets_factory_provider.get_item()
|
||||||
|
@ -1,11 +1,10 @@
|
|||||||
|
# -*- mode: python; indent-tabs-mode: nil; py-indent-offset: 4; coding: utf-8 -*-
|
||||||
|
|
||||||
from ui.group_peers_list import PeerItem, PeerTypeItem
|
from ui.group_peers_list import PeerItem, PeerTypeItem
|
||||||
from wrapper.toxcore_enums_and_consts import *
|
from toxygen_wrapper.toxcore_enums_and_consts import *
|
||||||
from ui.widgets import *
|
from ui.widgets import *
|
||||||
|
|
||||||
|
|
||||||
# -----------------------------------------------------------------------------------------------------------------
|
|
||||||
# Builder
|
# Builder
|
||||||
# -----------------------------------------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
|
|
||||||
class PeerListBuilder:
|
class PeerListBuilder:
|
||||||
@ -63,9 +62,7 @@ class PeerListBuilder:
|
|||||||
self._peers[self._index] = peer
|
self._peers[self._index] = peer
|
||||||
self._index += 1
|
self._index += 1
|
||||||
|
|
||||||
# -----------------------------------------------------------------------------------------------------------------
|
|
||||||
# Generators
|
# Generators
|
||||||
# -----------------------------------------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
|
|
||||||
class PeersListGenerator:
|
class PeersListGenerator:
|
||||||
|
@ -5,7 +5,7 @@ import utils.util as util
|
|||||||
|
|
||||||
global LOG
|
global LOG
|
||||||
import logging
|
import logging
|
||||||
LOG = logging.getLogger('app.db')
|
LOG = logging.getLogger('h.database')
|
||||||
|
|
||||||
TIMEOUT = 11
|
TIMEOUT = 11
|
||||||
SAVE_MESSAGES = 500
|
SAVE_MESSAGES = 500
|
||||||
@ -51,9 +51,7 @@ class Database:
|
|||||||
os.remove(path)
|
os.remove(path)
|
||||||
LOG.info('Db opened: ' +path)
|
LOG.info('Db opened: ' +path)
|
||||||
|
|
||||||
# -----------------------------------------------------------------------------------------------------------------
|
|
||||||
# Public methods
|
# Public methods
|
||||||
# -----------------------------------------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
def save(self):
|
def save(self):
|
||||||
if self._toxes.has_password():
|
if self._toxes.has_password():
|
||||||
@ -88,7 +86,7 @@ class Database:
|
|||||||
db.commit()
|
db.commit()
|
||||||
return True
|
return True
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
LOG.error("dd_friend_to_db " +self._name +' Database exception! ' +str(e))
|
LOG.error("dd_friend_to_db " +self._name +f" Database exception! {e}")
|
||||||
db.rollback()
|
db.rollback()
|
||||||
return False
|
return False
|
||||||
finally:
|
finally:
|
||||||
@ -103,7 +101,7 @@ class Database:
|
|||||||
db.commit()
|
db.commit()
|
||||||
return True
|
return True
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
LOG.error("delete_friend_from_db " +self._name +' Database exception! ' +str(e))
|
LOG.error("delete_friend_from_db " +self._name +f" Database exception! {e}")
|
||||||
db.rollback()
|
db.rollback()
|
||||||
return False
|
return False
|
||||||
finally:
|
finally:
|
||||||
@ -120,7 +118,7 @@ class Database:
|
|||||||
db.commit()
|
db.commit()
|
||||||
return True
|
return True
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
LOG.error("" +self._name +' Database exception! ' +str(e))
|
LOG.error("save_messages_to_db" +self._name +f" Database exception! {e}")
|
||||||
db.rollback()
|
db.rollback()
|
||||||
return False
|
return False
|
||||||
finally:
|
finally:
|
||||||
@ -136,7 +134,7 @@ class Database:
|
|||||||
db.commit()
|
db.commit()
|
||||||
return True
|
return True
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
LOG.error("" +self._name +' Database exception! ' +str(e))
|
LOG.error("update_messages" +self._name +f" Database exception! {e}")
|
||||||
db.rollback()
|
db.rollback()
|
||||||
return False
|
return False
|
||||||
finally:
|
finally:
|
||||||
@ -151,7 +149,7 @@ class Database:
|
|||||||
db.commit()
|
db.commit()
|
||||||
return True
|
return True
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
LOG.error("" +self._name +' Database exception! ' +str(e))
|
LOG.error("delete_message" +self._name +f" Database exception! {e}")
|
||||||
db.rollback()
|
db.rollback()
|
||||||
return False
|
return False
|
||||||
finally:
|
finally:
|
||||||
@ -166,7 +164,7 @@ class Database:
|
|||||||
db.commit()
|
db.commit()
|
||||||
return True
|
return True
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
LOG.error("" +self._name +' Database exception! ' +str(e))
|
LOG.error("delete_messages" +self._name +f" Database exception! {e}")
|
||||||
db.rollback()
|
db.rollback()
|
||||||
return False
|
return False
|
||||||
finally:
|
finally:
|
||||||
@ -178,9 +176,7 @@ class Database:
|
|||||||
|
|
||||||
return Database.MessageGetter(self._path, tox_id)
|
return Database.MessageGetter(self._path, tox_id)
|
||||||
|
|
||||||
# -----------------------------------------------------------------------------------------------------------------
|
|
||||||
# Messages loading
|
# Messages loading
|
||||||
# -----------------------------------------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
class MessageGetter:
|
class MessageGetter:
|
||||||
|
|
||||||
@ -225,9 +221,7 @@ class Database:
|
|||||||
def _disconnect(self):
|
def _disconnect(self):
|
||||||
self._db.close()
|
self._db.close()
|
||||||
|
|
||||||
# -----------------------------------------------------------------------------------------------------------------
|
|
||||||
# Private methods
|
# Private methods
|
||||||
# -----------------------------------------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
def _connect(self):
|
def _connect(self):
|
||||||
return connect(self._path, timeout=TIMEOUT)
|
return connect(self._path, timeout=TIMEOUT)
|
||||||
|
@ -22,9 +22,7 @@ class History:
|
|||||||
def set_contacts_manager(self, contacts_manager):
|
def set_contacts_manager(self, contacts_manager):
|
||||||
self._contacts_manager = contacts_manager
|
self._contacts_manager = contacts_manager
|
||||||
|
|
||||||
# -----------------------------------------------------------------------------------------------------------------
|
|
||||||
# History support
|
# History support
|
||||||
# -----------------------------------------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
def save_history(self):
|
def save_history(self):
|
||||||
"""
|
"""
|
||||||
@ -128,9 +126,7 @@ class History:
|
|||||||
|
|
||||||
return generator.generate()
|
return generator.generate()
|
||||||
|
|
||||||
# -----------------------------------------------------------------------------------------------------------------
|
|
||||||
# Items creation
|
# Items creation
|
||||||
# -----------------------------------------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
def _create_message_item(self, message):
|
def _create_message_item(self, message):
|
||||||
return self._messages_items_factory.create_message_item(message, False)
|
return self._messages_items_factory.create_message_item(message, False)
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
# -*- mode: python; indent-tabs-mode: nil; py-indent-offset: 4; coding: utf-8 -*-
|
||||||
|
|
||||||
import utils.util as util
|
import utils.util as util
|
||||||
from messenger.messages import *
|
from messenger.messages import *
|
||||||
|
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
# -*- mode: python; indent-tabs-mode: nil; py-indent-offset: 4; coding: utf-8 -*-
|
||||||
|
|
||||||
import os.path
|
import os.path
|
||||||
|
|
||||||
from history.database import MESSAGE_AUTHOR
|
from history.database import MESSAGE_AUTHOR
|
||||||
@ -67,7 +69,7 @@ class Message:
|
|||||||
|
|
||||||
def get_widget(self, *args):
|
def get_widget(self, *args):
|
||||||
# FixMe
|
# FixMe
|
||||||
self._widget = self._create_widget(*args)
|
self._widget = self._create_widget(*args) # pylint: disable=assignment-from-none
|
||||||
|
|
||||||
return self._widget
|
return self._widget
|
||||||
|
|
||||||
@ -83,10 +85,10 @@ class Message:
|
|||||||
|
|
||||||
def _create_widget(self, *args):
|
def _create_widget(self, *args):
|
||||||
# overridden
|
# overridden
|
||||||
pass
|
return None
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _get_id():
|
def _get_id() -> int:
|
||||||
Message.MESSAGE_ID += 1
|
Message.MESSAGE_ID += 1
|
||||||
|
|
||||||
return int(Message.MESSAGE_ID)
|
return int(Message.MESSAGE_ID)
|
||||||
@ -102,7 +104,7 @@ class TextMessage(Message):
|
|||||||
self._message = message
|
self._message = message
|
||||||
self._id = message_id
|
self._id = message_id
|
||||||
|
|
||||||
def get_text(self):
|
def get_text(self) -> str:
|
||||||
return self._message
|
return self._message
|
||||||
|
|
||||||
text = property(get_text)
|
text = property(get_text)
|
||||||
@ -136,8 +138,8 @@ class OutgoingTextMessage(TextMessage):
|
|||||||
|
|
||||||
class GroupChatMessage(TextMessage):
|
class GroupChatMessage(TextMessage):
|
||||||
|
|
||||||
def __init__(self, id, message, owner, iTime, message_type, name):
|
def __init__(self, cid, message, owner, iTime, message_type, name):
|
||||||
super().__init__(id, message, owner, iTime, message_type)
|
super().__init__(cid, message, owner, iTime, message_type)
|
||||||
self._user_name = name
|
self._user_name = name
|
||||||
|
|
||||||
|
|
||||||
@ -153,13 +155,13 @@ class TransferMessage(Message):
|
|||||||
self._file_name = file_name
|
self._file_name = file_name
|
||||||
self._friend_number, self._file_number = friend_number, file_number
|
self._friend_number, self._file_number = friend_number, file_number
|
||||||
|
|
||||||
def is_active(self, file_number):
|
def is_active(self, file_number) -> bool:
|
||||||
if self._file_number != file_number:
|
if self._file_number != file_number:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
return self._state not in (FILE_TRANSFER_STATE['FINISHED'], FILE_TRANSFER_STATE['CANCELLED'])
|
return self._state not in (FILE_TRANSFER_STATE['FINISHED'], FILE_TRANSFER_STATE['CANCELLED'])
|
||||||
|
|
||||||
def get_friend_number(self):
|
def get_friend_number(self) -> int:
|
||||||
return self._friend_number
|
return self._friend_number
|
||||||
|
|
||||||
friend_number = property(get_friend_number)
|
friend_number = property(get_friend_number)
|
||||||
|
@ -1,13 +1,13 @@
|
|||||||
# -*- mode: python; indent-tabs-mode: nil; py-indent-offset: 4; coding: utf-8 -*-
|
# -*- mode: python; indent-tabs-mode: nil; py-indent-offset: 4; coding: utf-8 -*-
|
||||||
|
import logging
|
||||||
import common.tox_save as tox_save
|
import common.tox_save as tox_save
|
||||||
import utils.ui as util_ui
|
import utils.ui as util_ui
|
||||||
|
|
||||||
from messenger.messages import *
|
from messenger.messages import *
|
||||||
from wrapper_tests.support_testing import assert_main_thread
|
from toxygen_wrapper.tests.support_testing import assert_main_thread
|
||||||
from wrapper.toxcore_enums_and_consts import TOX_MAX_MESSAGE_LENGTH
|
from toxygen_wrapper.toxcore_enums_and_consts import TOX_MAX_MESSAGE_LENGTH
|
||||||
|
|
||||||
global LOG
|
global LOG
|
||||||
import logging
|
|
||||||
LOG = logging.getLogger('app.'+__name__)
|
LOG = logging.getLogger('app.'+__name__)
|
||||||
log = lambda x: LOG.info(x)
|
log = lambda x: LOG.info(x)
|
||||||
|
|
||||||
@ -31,18 +31,16 @@ class Messenger(tox_save.ToxSave):
|
|||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return "<Messenger>"
|
return "<Messenger>"
|
||||||
|
|
||||||
def get_last_message(self):
|
def get_last_message(self) -> str:
|
||||||
contact = self._contacts_manager.get_curr_contact()
|
contact = self._contacts_manager.get_curr_contact()
|
||||||
if contact is None:
|
if contact is None:
|
||||||
return str()
|
return str()
|
||||||
|
|
||||||
return contact.get_last_message_text()
|
return contact.get_last_message_text()
|
||||||
|
|
||||||
# -----------------------------------------------------------------------------------------------------------------
|
|
||||||
# Messaging - friends
|
# Messaging - friends
|
||||||
# -----------------------------------------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
def new_message(self, friend_number, message_type, message):
|
def new_message(self, friend_number, message_type, message) -> None:
|
||||||
"""
|
"""
|
||||||
Current user gets new message
|
Current user gets new message
|
||||||
:param friend_number: friend_num of friend who sent message
|
:param friend_number: friend_num of friend who sent message
|
||||||
@ -54,7 +52,7 @@ class Messenger(tox_save.ToxSave):
|
|||||||
text_message = TextMessage(message, MessageAuthor(friend.name, MESSAGE_AUTHOR['FRIEND']), t, message_type)
|
text_message = TextMessage(message, MessageAuthor(friend.name, MESSAGE_AUTHOR['FRIEND']), t, message_type)
|
||||||
self._add_message(text_message, friend)
|
self._add_message(text_message, friend)
|
||||||
|
|
||||||
def send_message(self):
|
def send_message(self) -> None:
|
||||||
text = self._screen.messageEdit.toPlainText()
|
text = self._screen.messageEdit.toPlainText()
|
||||||
|
|
||||||
plugin_command_prefix = '/plugin '
|
plugin_command_prefix = '/plugin '
|
||||||
@ -90,7 +88,7 @@ class Messenger(tox_save.ToxSave):
|
|||||||
assert_main_thread()
|
assert_main_thread()
|
||||||
util_ui.message_box(text, title)
|
util_ui.message_box(text, title)
|
||||||
|
|
||||||
def send_message_to_friend(self, text, message_type, friend_number=None):
|
def send_message_to_friend(self, text, message_type, friend_number=None) -> None:
|
||||||
"""
|
"""
|
||||||
Send message
|
Send message
|
||||||
:param text: message text
|
:param text: message text
|
||||||
@ -126,7 +124,7 @@ class Messenger(tox_save.ToxSave):
|
|||||||
self._screen.messageEdit.clear()
|
self._screen.messageEdit.clear()
|
||||||
self._screen.messages.scrollToBottom()
|
self._screen.messages.scrollToBottom()
|
||||||
|
|
||||||
def send_messages(self, friend_number):
|
def send_messages(self, friend_number:int) -> None:
|
||||||
"""
|
"""
|
||||||
Send 'offline' messages to friend
|
Send 'offline' messages to friend
|
||||||
"""
|
"""
|
||||||
@ -140,11 +138,9 @@ class Messenger(tox_save.ToxSave):
|
|||||||
except Exception as ex:
|
except Exception as ex:
|
||||||
LOG.warn('Sending pending messages failed with ' + str(ex))
|
LOG.warn('Sending pending messages failed with ' + str(ex))
|
||||||
|
|
||||||
# -----------------------------------------------------------------------------------------------------------------
|
|
||||||
# Messaging - groups
|
# Messaging - groups
|
||||||
# -----------------------------------------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
def send_message_to_group(self, text, message_type, group_number=None):
|
def send_message_to_group(self, text, message_type, group_number=None) -> None:
|
||||||
if group_number is None:
|
if group_number is None:
|
||||||
group_number = self._contacts_manager.get_active_number()
|
group_number = self._contacts_manager.get_active_number()
|
||||||
|
|
||||||
@ -165,7 +161,7 @@ class Messenger(tox_save.ToxSave):
|
|||||||
self._screen.messageEdit.clear()
|
self._screen.messageEdit.clear()
|
||||||
self._screen.messages.scrollToBottom()
|
self._screen.messages.scrollToBottom()
|
||||||
|
|
||||||
def new_group_message(self, group_number, message_type, message, peer_id):
|
def new_group_message(self, group_number, message_type, message, peer_id) -> None:
|
||||||
"""
|
"""
|
||||||
Current user gets new message
|
Current user gets new message
|
||||||
:param message_type: message type - plain text or action message (/me)
|
:param message_type: message type - plain text or action message (/me)
|
||||||
@ -183,11 +179,9 @@ class Messenger(tox_save.ToxSave):
|
|||||||
text_message = TextMessage(message, MessageAuthor(peer.name, MESSAGE_AUTHOR['GC_PEER']), t, message_type)
|
text_message = TextMessage(message, MessageAuthor(peer.name, MESSAGE_AUTHOR['GC_PEER']), t, message_type)
|
||||||
self._add_message(text_message, group)
|
self._add_message(text_message, group)
|
||||||
|
|
||||||
# -----------------------------------------------------------------------------------------------------------------
|
|
||||||
# Messaging - group peers
|
# Messaging - group peers
|
||||||
# -----------------------------------------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
def send_message_to_group_peer(self, text, message_type, group_number=None, peer_id=None):
|
def send_message_to_group_peer(self, text, message_type, group_number=None, peer_id=None) -> None:
|
||||||
if group_number is None or peer_id is None:
|
if group_number is None or peer_id is None:
|
||||||
group_peer_contact = self._contacts_manager.get_curr_contact()
|
group_peer_contact = self._contacts_manager.get_curr_contact()
|
||||||
peer_id = group_peer_contact.number
|
peer_id = group_peer_contact.number
|
||||||
@ -198,28 +192,40 @@ class Messenger(tox_save.ToxSave):
|
|||||||
return
|
return
|
||||||
if group.number < 0:
|
if group.number < 0:
|
||||||
return
|
return
|
||||||
if peer_id and peer_id < 0:
|
if peer_id is not None and peer_id < 0:
|
||||||
return
|
return
|
||||||
|
|
||||||
assert_main_thread()
|
assert_main_thread()
|
||||||
# FixMe: peer_id is None?
|
|
||||||
group_peer_contact = self._contacts_manager.get_or_create_group_peer_contact(group_number, peer_id)
|
|
||||||
# group_peer_contact now may be None
|
|
||||||
group = self._get_group_by_number(group_number)
|
group = self._get_group_by_number(group_number)
|
||||||
messages = self._split_message(text.encode('utf-8'))
|
messages = self._split_message(text.encode('utf-8'))
|
||||||
|
|
||||||
|
# FixMe: peer_id is None?
|
||||||
|
group_peer_contact = self._contacts_manager.get_or_create_group_peer_contact(group_number, peer_id)
|
||||||
|
if group_peer_contact is None:
|
||||||
|
LOG.warn("M.group_send_private_message group_peer_contact is None")
|
||||||
|
return
|
||||||
|
# group_peer_contact now may be None
|
||||||
|
|
||||||
t = util.get_unix_time()
|
t = util.get_unix_time()
|
||||||
for message in messages:
|
for message in messages:
|
||||||
self._tox.group_send_private_message(group_number, peer_id, message_type, message)
|
bRet = self._tox.group_send_private_message(group_number, peer_id, message_type, message)
|
||||||
|
if not bRet:
|
||||||
|
LOG.warn("M.group_send_private_messag failed")
|
||||||
|
continue
|
||||||
message_author = MessageAuthor(group.get_self_name(), MESSAGE_AUTHOR['GC_PEER'])
|
message_author = MessageAuthor(group.get_self_name(), MESSAGE_AUTHOR['GC_PEER'])
|
||||||
message = OutgoingTextMessage(text, message_author, t, message_type)
|
message = OutgoingTextMessage(text, message_author, t, message_type)
|
||||||
group_peer_contact.append_message(message)
|
# AttributeError: 'GroupChatPeer' object has no attribute 'append_message'
|
||||||
|
if not hasattr(group_peer_contact, 'append_message'):
|
||||||
|
LOG.warn("M. group_peer_contact has no append_message group_peer_contact={group_peer_contact}")
|
||||||
|
else:
|
||||||
|
group_peer_contact.append_message(message)
|
||||||
if not self._contacts_manager.is_contact_active(group_peer_contact):
|
if not self._contacts_manager.is_contact_active(group_peer_contact):
|
||||||
return
|
return
|
||||||
self._create_message_item(message)
|
self._create_message_item(message)
|
||||||
self._screen.messageEdit.clear()
|
self._screen.messageEdit.clear()
|
||||||
self._screen.messages.scrollToBottom()
|
self._screen.messages.scrollToBottom()
|
||||||
|
|
||||||
def new_group_private_message(self, group_number, message_type, message, peer_id):
|
def new_group_private_message(self, group_number, message_type, message, peer_id) -> None:
|
||||||
"""
|
"""
|
||||||
Current user gets new message
|
Current user gets new message
|
||||||
:param message: text of message
|
:param message: text of message
|
||||||
@ -238,19 +244,15 @@ class Messenger(tox_save.ToxSave):
|
|||||||
return
|
return
|
||||||
self._add_message(text_message, group_peer_contact)
|
self._add_message(text_message, group_peer_contact)
|
||||||
|
|
||||||
# -----------------------------------------------------------------------------------------------------------------
|
|
||||||
# Message receipts
|
# Message receipts
|
||||||
# -----------------------------------------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
def receipt(self, friend_number, message_id):
|
def receipt(self, friend_number, message_id) -> None:
|
||||||
friend = self._get_friend_by_number(friend_number)
|
friend = self._get_friend_by_number(friend_number)
|
||||||
friend.mark_as_sent(message_id)
|
friend.mark_as_sent(message_id)
|
||||||
|
|
||||||
# -----------------------------------------------------------------------------------------------------------------
|
|
||||||
# Typing notifications
|
# Typing notifications
|
||||||
# -----------------------------------------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
def send_typing(self, typing):
|
def send_typing(self, typing) -> None:
|
||||||
"""
|
"""
|
||||||
Send typing notification to a friend
|
Send typing notification to a friend
|
||||||
"""
|
"""
|
||||||
@ -259,18 +261,16 @@ class Messenger(tox_save.ToxSave):
|
|||||||
contact = self._contacts_manager.get_curr_contact()
|
contact = self._contacts_manager.get_curr_contact()
|
||||||
contact.typing_notification_handler.send(self._tox, typing)
|
contact.typing_notification_handler.send(self._tox, typing)
|
||||||
|
|
||||||
def friend_typing(self, friend_number, typing):
|
def friend_typing(self, friend_number, typing) -> None:
|
||||||
"""
|
"""
|
||||||
Display incoming typing notification
|
Display incoming typing notification
|
||||||
"""
|
"""
|
||||||
if self._contacts_manager.is_friend_active(friend_number):
|
if self._contacts_manager.is_friend_active(friend_number):
|
||||||
self._screen.typing.setVisible(typing)
|
self._screen.typing.setVisible(typing)
|
||||||
|
|
||||||
# -----------------------------------------------------------------------------------------------------------------
|
|
||||||
# Contact info updated
|
# Contact info updated
|
||||||
# -----------------------------------------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
def new_friend_name(self, friend, old_name, new_name):
|
def new_friend_name(self, friend, old_name, new_name) -> None:
|
||||||
if old_name == new_name or friend.has_alias():
|
if old_name == new_name or friend.has_alias():
|
||||||
return
|
return
|
||||||
message = util_ui.tr('User {} is now known as {}')
|
message = util_ui.tr('User {} is now known as {}')
|
||||||
@ -279,12 +279,10 @@ class Messenger(tox_save.ToxSave):
|
|||||||
friend.actions = True
|
friend.actions = True
|
||||||
self._add_info_message(friend.number, message)
|
self._add_info_message(friend.number, message)
|
||||||
|
|
||||||
# -----------------------------------------------------------------------------------------------------------------
|
|
||||||
# Private methods
|
# Private methods
|
||||||
# -----------------------------------------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _split_message(message):
|
def _split_message(message) -> list:
|
||||||
messages = []
|
messages = []
|
||||||
while len(message) > TOX_MAX_MESSAGE_LENGTH:
|
while len(message) > TOX_MAX_MESSAGE_LENGTH:
|
||||||
size = TOX_MAX_MESSAGE_LENGTH * 4 // 5
|
size = TOX_MAX_MESSAGE_LENGTH * 4 // 5
|
||||||
@ -305,7 +303,7 @@ class Messenger(tox_save.ToxSave):
|
|||||||
|
|
||||||
return messages
|
return messages
|
||||||
|
|
||||||
def _get_friend_by_number(self, friend_number):
|
def _get_friend_by_number(self, friend_number:int):
|
||||||
return self._contacts_provider.get_friend_by_number(friend_number)
|
return self._contacts_provider.get_friend_by_number(friend_number)
|
||||||
|
|
||||||
def _get_group_by_number(self, group_number):
|
def _get_group_by_number(self, group_number):
|
||||||
@ -314,7 +312,7 @@ class Messenger(tox_save.ToxSave):
|
|||||||
def _get_group_by_public_key(self, public_key):
|
def _get_group_by_public_key(self, public_key):
|
||||||
return self._contacts_provider.get_group_by_public_key( public_key)
|
return self._contacts_provider.get_group_by_public_key( public_key)
|
||||||
|
|
||||||
def _on_profile_name_changed(self, new_name):
|
def _on_profile_name_changed(self, new_name) -> None:
|
||||||
if self._profile_name == new_name:
|
if self._profile_name == new_name:
|
||||||
return
|
return
|
||||||
message = util_ui.tr('User {} is now known as {}')
|
message = util_ui.tr('User {} is now known as {}')
|
||||||
@ -323,18 +321,18 @@ class Messenger(tox_save.ToxSave):
|
|||||||
self._add_info_message(friend.number, message)
|
self._add_info_message(friend.number, message)
|
||||||
self._profile_name = new_name
|
self._profile_name = new_name
|
||||||
|
|
||||||
def _on_call_started(self, friend_number, audio, video, is_outgoing):
|
def _on_call_started(self, friend_number, audio, video, is_outgoing) -> None:
|
||||||
if is_outgoing:
|
if is_outgoing:
|
||||||
text = util_ui.tr("Outgoing video call") if video else util_ui.tr("Outgoing audio call")
|
text = util_ui.tr("Outgoing video call") if video else util_ui.tr("Outgoing audio call")
|
||||||
else:
|
else:
|
||||||
text = util_ui.tr("Incoming video call") if video else util_ui.tr("Incoming audio call")
|
text = util_ui.tr("Incoming video call") if video else util_ui.tr("Incoming audio call")
|
||||||
self._add_info_message(friend_number, text)
|
self._add_info_message(friend_number, text)
|
||||||
|
|
||||||
def _on_call_finished(self, friend_number, is_declined):
|
def _on_call_finished(self, friend_number, is_declined) -> None:
|
||||||
text = util_ui.tr("Call declined") if is_declined else util_ui.tr("Call finished")
|
text = util_ui.tr("Call declined") if is_declined else util_ui.tr("Call finished")
|
||||||
self._add_info_message(friend_number, text)
|
self._add_info_message(friend_number, text)
|
||||||
|
|
||||||
def _add_info_message(self, friend_number, text):
|
def _add_info_message(self, friend_number, text) -> None:
|
||||||
friend = self._get_friend_by_number(friend_number)
|
friend = self._get_friend_by_number(friend_number)
|
||||||
assert friend
|
assert friend
|
||||||
message = InfoMessage(text, util.get_unix_time())
|
message = InfoMessage(text, util.get_unix_time())
|
||||||
@ -342,12 +340,12 @@ class Messenger(tox_save.ToxSave):
|
|||||||
if self._contacts_manager.is_friend_active(friend_number):
|
if self._contacts_manager.is_friend_active(friend_number):
|
||||||
self._create_info_message_item(message)
|
self._create_info_message_item(message)
|
||||||
|
|
||||||
def _create_info_message_item(self, message):
|
def _create_info_message_item(self, message) -> None:
|
||||||
assert_main_thread()
|
assert_main_thread()
|
||||||
self._items_factory.create_message_item(message)
|
self._items_factory.create_message_item(message)
|
||||||
self._screen.messages.scrollToBottom()
|
self._screen.messages.scrollToBottom()
|
||||||
|
|
||||||
def _add_message(self, text_message, contact):
|
def _add_message(self, text_message, contact) -> None:
|
||||||
assert_main_thread()
|
assert_main_thread()
|
||||||
if not contact:
|
if not contact:
|
||||||
LOG.warn("_add_message null contact")
|
LOG.warn("_add_message null contact")
|
||||||
@ -364,6 +362,6 @@ class Messenger(tox_save.ToxSave):
|
|||||||
if not contact.visibility:
|
if not contact.visibility:
|
||||||
self._contacts_manager.update_filtration()
|
self._contacts_manager.update_filtration()
|
||||||
|
|
||||||
def _create_message_item(self, text_message):
|
def _create_message_item(self, text_message) -> None:
|
||||||
# pixmap = self._contacts_manager.get_curr_contact().get_pixmap()
|
# pixmap = self._contacts_manager.get_curr_contact().get_pixmap()
|
||||||
self._items_factory.create_message_item(text_message)
|
self._items_factory.create_message_item(text_message)
|
||||||
|
@ -2,10 +2,10 @@
|
|||||||
import sys
|
import sys
|
||||||
import os
|
import os
|
||||||
import threading
|
import threading
|
||||||
from PyQt5 import QtGui
|
from qtpy import QtGui
|
||||||
from wrapper.toxcore_enums_and_consts import *
|
from toxygen_wrapper.toxcore_enums_and_consts import *
|
||||||
from wrapper.toxav_enums import *
|
from toxygen_wrapper.toxav_enums import *
|
||||||
from wrapper.tox import bin_to_string
|
from toxygen_wrapper.tox import bin_to_string
|
||||||
import utils.ui as util_ui
|
import utils.ui as util_ui
|
||||||
import utils.util as util
|
import utils.util as util
|
||||||
from middleware.threads import invoke_in_main_thread, execute
|
from middleware.threads import invoke_in_main_thread, execute
|
||||||
@ -15,17 +15,17 @@ from datetime import datetime
|
|||||||
|
|
||||||
iMAX_INT32 = 4294967295
|
iMAX_INT32 = 4294967295
|
||||||
# callbacks can be called in any thread so were being careful
|
# callbacks can be called in any thread so were being careful
|
||||||
def LOG_ERROR(l): print('EROR< '+l)
|
def LOG_ERROR(l): print(f"EROR. {l}")
|
||||||
def LOG_WARN(l): print('WARN< '+l)
|
def LOG_WARN(l): print(f"WARN. {l}")
|
||||||
def LOG_INFO(l):
|
def LOG_INFO(l):
|
||||||
bIsVerbose = hasattr(__builtins__, 'app') and app.oArgs.loglevel <= 20-1
|
bIsVerbose = not hasattr(__builtins__, 'app') or app.oArgs.loglevel <= 20-1 # pylint dusable=undefined-variable
|
||||||
if bIsVerbose: print('INFO< '+l)
|
if bIsVerbose: print(f"INFO. {l}")
|
||||||
def LOG_DEBUG(l):
|
def LOG_DEBUG(l):
|
||||||
bIsVerbose = hasattr(__builtins__, 'app') and app.oArgs.loglevel <= 10-1
|
bIsVerbose = not hasattr(__builtins__, 'app') or app.oArgs.loglevel <= 10-1 # pylint dusable=undefined-variable
|
||||||
if bIsVerbose: print('DBUG< '+l)
|
if bIsVerbose: print(f"DBUG. {l}")
|
||||||
def LOG_TRACE(l):
|
def LOG_TRACE(l):
|
||||||
bIsVerbose = hasattr(__builtins__, 'app') and app.oArgs.loglevel < 10-1
|
bIsVerbose = not hasattr(__builtins__, 'app') or app.oArgs.loglevel < 10-1 # pylint dusable=undefined-variable
|
||||||
pass # print('TRACE+ '+l)
|
pass # print(f"TRACE. {l}")
|
||||||
|
|
||||||
global aTIMES
|
global aTIMES
|
||||||
aTIMES=dict()
|
aTIMES=dict()
|
||||||
@ -46,9 +46,7 @@ def bTooSoon(key, sSlot, fSec=10.0):
|
|||||||
|
|
||||||
# TODO: refactoring. Use contact provider instead of manager
|
# TODO: refactoring. Use contact provider instead of manager
|
||||||
|
|
||||||
# -----------------------------------------------------------------------------------------------------------------
|
|
||||||
# Callbacks - current user
|
# Callbacks - current user
|
||||||
# -----------------------------------------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
global iBYTES
|
global iBYTES
|
||||||
iBYTES=0
|
iBYTES=0
|
||||||
@ -95,9 +93,7 @@ def self_connection_status(tox, profile):
|
|||||||
return wrapped
|
return wrapped
|
||||||
|
|
||||||
|
|
||||||
# -----------------------------------------------------------------------------------------------------------------
|
|
||||||
# Callbacks - friends
|
# Callbacks - friends
|
||||||
# -----------------------------------------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
|
|
||||||
def friend_status(contacts_manager, file_transfer_handler, profile, settings):
|
def friend_status(contacts_manager, file_transfer_handler, profile, settings):
|
||||||
@ -106,7 +102,7 @@ def friend_status(contacts_manager, file_transfer_handler, profile, settings):
|
|||||||
"""
|
"""
|
||||||
Check friend's status (none, busy, away)
|
Check friend's status (none, busy, away)
|
||||||
"""
|
"""
|
||||||
LOG_DEBUG(f"Friend's #{friend_number} status changed")
|
LOG_INFO(f"Friend's #{friend_number} status changed")
|
||||||
key = f"friend_number {friend_number}"
|
key = f"friend_number {friend_number}"
|
||||||
if bTooSoon(key, sSlot, 10): return
|
if bTooSoon(key, sSlot, 10): return
|
||||||
friend = contacts_manager.get_friend_by_number(friend_number)
|
friend = contacts_manager.get_friend_by_number(friend_number)
|
||||||
@ -235,9 +231,7 @@ def friend_read_receipt(messenger):
|
|||||||
return wrapped
|
return wrapped
|
||||||
|
|
||||||
|
|
||||||
# -----------------------------------------------------------------------------------------------------------------
|
|
||||||
# Callbacks - file transfers
|
# Callbacks - file transfers
|
||||||
# -----------------------------------------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
|
|
||||||
def tox_file_recv(window, tray, profile, file_transfer_handler, contacts_manager, settings):
|
def tox_file_recv(window, tray, profile, file_transfer_handler, contacts_manager, settings):
|
||||||
@ -246,7 +240,7 @@ def tox_file_recv(window, tray, profile, file_transfer_handler, contacts_manager
|
|||||||
"""
|
"""
|
||||||
def wrapped(tox, friend_number, file_number, file_type, size, file_name, file_name_size, user_data):
|
def wrapped(tox, friend_number, file_number, file_type, size, file_name, file_name_size, user_data):
|
||||||
if file_type == TOX_FILE_KIND['DATA']:
|
if file_type == TOX_FILE_KIND['DATA']:
|
||||||
LOG_DEBUG(f'file_transfer_handler File')
|
LOG_INFO(f'file_transfer_handler File friend_number={friend_number}')
|
||||||
try:
|
try:
|
||||||
file_name = str(file_name[:file_name_size], 'utf-8')
|
file_name = str(file_name[:file_name_size], 'utf-8')
|
||||||
except:
|
except:
|
||||||
@ -312,9 +306,7 @@ def file_recv_control(file_transfer_handler):
|
|||||||
|
|
||||||
return wrapped
|
return wrapped
|
||||||
|
|
||||||
# -----------------------------------------------------------------------------------------------------------------
|
|
||||||
# Callbacks - custom packets
|
# Callbacks - custom packets
|
||||||
# -----------------------------------------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
|
|
||||||
def lossless_packet(plugin_loader):
|
def lossless_packet(plugin_loader):
|
||||||
@ -339,9 +331,7 @@ def lossy_packet(plugin_loader):
|
|||||||
return wrapped
|
return wrapped
|
||||||
|
|
||||||
|
|
||||||
# -----------------------------------------------------------------------------------------------------------------
|
|
||||||
# Callbacks - audio
|
# Callbacks - audio
|
||||||
# -----------------------------------------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
def call_state(calls_manager):
|
def call_state(calls_manager):
|
||||||
def wrapped(iToxav, friend_number, mask, user_data):
|
def wrapped(iToxav, friend_number, mask, user_data):
|
||||||
@ -375,7 +365,7 @@ def callback_audio(calls_manager):
|
|||||||
"""
|
"""
|
||||||
New audio chunk
|
New audio chunk
|
||||||
"""
|
"""
|
||||||
LOG_DEBUG(f"callback_audio #{friend_number}")
|
#trace LOG_DEBUG(f"callback_audio #{friend_number}")
|
||||||
# dunno was .call
|
# dunno was .call
|
||||||
calls_manager._call.audio_chunk(
|
calls_manager._call.audio_chunk(
|
||||||
bytes(samples[:audio_samples_per_channel * 2 * audio_channels_count]),
|
bytes(samples[:audio_samples_per_channel * 2 * audio_channels_count]),
|
||||||
@ -384,9 +374,7 @@ def callback_audio(calls_manager):
|
|||||||
|
|
||||||
return wrapped
|
return wrapped
|
||||||
|
|
||||||
# -----------------------------------------------------------------------------------------------------------------
|
|
||||||
# Callbacks - video
|
# Callbacks - video
|
||||||
# -----------------------------------------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
|
|
||||||
def video_receive_frame(toxav, friend_number, width, height, y, u, v, ystride, ustride, vstride, user_data):
|
def video_receive_frame(toxav, friend_number, width, height, y, u, v, ystride, ustride, vstride, user_data):
|
||||||
@ -414,7 +402,7 @@ def video_receive_frame(toxav, friend_number, width, height, y, u, v, ystride, u
|
|||||||
It can be created from initial y, u, v using slices
|
It can be created from initial y, u, v using slices
|
||||||
"""
|
"""
|
||||||
LOG_DEBUG(f"video_receive_frame from toxav_video_receive_frame_cb={friend_number}")
|
LOG_DEBUG(f"video_receive_frame from toxav_video_receive_frame_cb={friend_number}")
|
||||||
import cv2
|
with ts.ignoreStdout(): import cv2
|
||||||
import numpy as np
|
import numpy as np
|
||||||
try:
|
try:
|
||||||
y_size = abs(max(width, abs(ystride)))
|
y_size = abs(max(width, abs(ystride)))
|
||||||
@ -437,16 +425,14 @@ def video_receive_frame(toxav, friend_number, width, height, y, u, v, ystride, u
|
|||||||
frame[height * 5 // 4:, :width // 2] = v[:height // 2:2, :width // 2]
|
frame[height * 5 // 4:, :width // 2] = v[:height // 2:2, :width // 2]
|
||||||
frame[height * 5 // 4:, width // 2:] = v[1:height // 2:2, :width // 2]
|
frame[height * 5 // 4:, width // 2:] = v[1:height // 2:2, :width // 2]
|
||||||
|
|
||||||
frame = cv2.cvtColor(frame, cv2.COLOR_YUV2BGR_I420)
|
frame = cv2.cvtColor(frame, cv2.COLOR_YUV2BGR_I420) # pylint: disable=no-member
|
||||||
|
# imshow
|
||||||
invoke_in_main_thread(cv2.imshow, str(friend_number), frame)
|
invoke_in_main_thread(cv2.imshow, str(friend_number), frame) # pylint: disable=no-member
|
||||||
except Exception as ex:
|
except Exception as ex:
|
||||||
LOG_ERROR(f"video_receive_frame {ex} #{friend_number}")
|
LOG_ERROR(f"video_receive_frame {ex} #{friend_number}")
|
||||||
pass
|
pass
|
||||||
|
|
||||||
# -----------------------------------------------------------------------------------------------------------------
|
|
||||||
# Callbacks - groups
|
# Callbacks - groups
|
||||||
# -----------------------------------------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
|
|
||||||
def group_message(window, tray, tox, messenger, settings, profile):
|
def group_message(window, tray, tox, messenger, settings, profile):
|
||||||
@ -487,7 +473,11 @@ def group_private_message(window, tray, tox, messenger, settings, profile):
|
|||||||
if window.isActiveWindow():
|
if window.isActiveWindow():
|
||||||
return
|
return
|
||||||
bl = settings['notify_all_gc'] or profile.name in message
|
bl = settings['notify_all_gc'] or profile.name in message
|
||||||
name = tox.group_peer_get_name(group_number, peer_id)
|
try:
|
||||||
|
name = tox.group_peer_get_name(group_number, peer_id)
|
||||||
|
except Exception as e:
|
||||||
|
LOG_WARN("tox.group_peer_get_name {group_number} {peer_id}")
|
||||||
|
name = ''
|
||||||
if settings['notifications'] and settings['tray_icon'] \
|
if settings['notifications'] and settings['tray_icon'] \
|
||||||
and profile.status != TOX_USER_STATUS['BUSY'] \
|
and profile.status != TOX_USER_STATUS['BUSY'] \
|
||||||
and (not settings.locked) and bl:
|
and (not settings.locked) and bl:
|
||||||
@ -592,7 +582,7 @@ def group_peer_name(contacts_provider, groups_service):
|
|||||||
else:
|
else:
|
||||||
# FixMe: known signal to revalidate roles...
|
# FixMe: known signal to revalidate roles...
|
||||||
#_peers = [(p._name, p._peer_id) for p in group.get_peers()]
|
#_peers = [(p._name, p._peer_id) for p in group.get_peers()]
|
||||||
LOG_TRACE(f"remove_peer group {group!r} has no peer_id={peer_id} in _peers!r")
|
LOG_TRACE(f"remove_peer group {group} has no peer_id={peer_id} in _peers!r")
|
||||||
return
|
return
|
||||||
|
|
||||||
return wrapped
|
return wrapped
|
||||||
@ -607,7 +597,7 @@ def group_peer_status(contacts_provider, groups_service):
|
|||||||
peer.status = peer_status
|
peer.status = peer_status
|
||||||
else:
|
else:
|
||||||
# _peers = [(p._name, p._peer_id) for p in group.get_peers()]
|
# _peers = [(p._name, p._peer_id) for p in group.get_peers()]
|
||||||
LOG_TRACE(f"remove_peer group {group!r} has no peer_id={peer_id} in _peers!r")
|
LOG_TRACE(f"remove_peer group {group} has no peer_id={peer_id} in _peers!r")
|
||||||
# TODO: add info message
|
# TODO: add info message
|
||||||
invoke_in_main_thread(groups_service.generate_peers_list)
|
invoke_in_main_thread(groups_service.generate_peers_list)
|
||||||
|
|
||||||
@ -623,7 +613,7 @@ def group_topic(contacts_provider):
|
|||||||
invoke_in_main_thread(group.set_status_message, topic)
|
invoke_in_main_thread(group.set_status_message, topic)
|
||||||
else:
|
else:
|
||||||
_peers = [(p._name, p._peer_id) for p in group.get_peers()]
|
_peers = [(p._name, p._peer_id) for p in group.get_peers()]
|
||||||
LOG_WARN(f"group_topic {group!r} has no peer_id={peer_id} in {_peers!r}")
|
LOG_WARN(f"group_topic {group} has no peer_id={peer_id} in {_peers}")
|
||||||
# TODO: add info message
|
# TODO: add info message
|
||||||
|
|
||||||
return wrapped
|
return wrapped
|
||||||
@ -637,7 +627,7 @@ def group_moderation(groups_service, contacts_provider, contacts_manager, messen
|
|||||||
else:
|
else:
|
||||||
# FixMe: known signal to revalidate roles...
|
# FixMe: known signal to revalidate roles...
|
||||||
# _peers = [(p._name, p._peer_id) for p in group.get_peers()]
|
# _peers = [(p._name, p._peer_id) for p in group.get_peers()]
|
||||||
LOG_TRACE(f"update_peer_role group {group!r} has no peer_id={peer_id} in _peers!r")
|
LOG_TRACE(f"update_peer_role group {group} has no peer_id={peer_id} in _peers!r")
|
||||||
# TODO: add info message
|
# TODO: add info message
|
||||||
|
|
||||||
def remove_peer(group, mod_peer_id, peer_id, is_ban):
|
def remove_peer(group, mod_peer_id, peer_id, is_ban):
|
||||||
@ -648,7 +638,7 @@ def group_moderation(groups_service, contacts_provider, contacts_manager, messen
|
|||||||
else:
|
else:
|
||||||
# FixMe: known signal to revalidate roles...
|
# FixMe: known signal to revalidate roles...
|
||||||
#_peers = [(p._name, p._peer_id) for p in group.get_peers()]
|
#_peers = [(p._name, p._peer_id) for p in group.get_peers()]
|
||||||
LOG_TRACE(f"remove_peer group {group!r} has no peer_id={peer_id} in _peers!r")
|
LOG_TRACE(f"remove_peer group {group} has no peer_id={peer_id} in _peers!r")
|
||||||
# TODO: add info message
|
# TODO: add info message
|
||||||
|
|
||||||
# source_peer_number, target_peer_number,
|
# source_peer_number, target_peer_number,
|
||||||
@ -661,13 +651,13 @@ def group_moderation(groups_service, contacts_provider, contacts_manager, messen
|
|||||||
mod_peer = group.get_peer_by_id(mod_peer_id)
|
mod_peer = group.get_peer_by_id(mod_peer_id)
|
||||||
if not mod_peer:
|
if not mod_peer:
|
||||||
#_peers = [(p._name, p._peer_id) for p in group.get_peers()]
|
#_peers = [(p._name, p._peer_id) for p in group.get_peers()]
|
||||||
LOG_TRACE(f"remove_peer group {group!r} has no mod_peer_id={mod_peer_id} in _peers!r")
|
LOG_TRACE(f"remove_peer group {group} has no mod_peer_id={mod_peer_id} in _peers!r")
|
||||||
return
|
return
|
||||||
peer = group.get_peer_by_id(peer_id)
|
peer = group.get_peer_by_id(peer_id)
|
||||||
if not peer:
|
if not peer:
|
||||||
# FixMe: known signal to revalidate roles...
|
# FixMe: known signal to revalidate roles...
|
||||||
#_peers = [(p._name, p._peer_id) for p in group.get_peers()]
|
#_peers = [(p._name, p._peer_id) for p in group.get_peers()]
|
||||||
LOG_TRACE(f"remove_peer group {group!r} has no peer_id={peer_id} in _peers!r")
|
LOG_TRACE(f"remove_peer group {group} has no peer_id={peer_id} in _peers!r")
|
||||||
return
|
return
|
||||||
|
|
||||||
if event_type == TOX_GROUP_MOD_EVENT['KICK']:
|
if event_type == TOX_GROUP_MOD_EVENT['KICK']:
|
||||||
@ -714,9 +704,7 @@ def group_privacy_state(contacts_provider):
|
|||||||
|
|
||||||
return wrapped
|
return wrapped
|
||||||
|
|
||||||
# -----------------------------------------------------------------------------------------------------------------
|
|
||||||
# Callbacks - initialization
|
# Callbacks - initialization
|
||||||
# -----------------------------------------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
|
|
||||||
def init_callbacks(tox, profile, settings, plugin_loader, contacts_manager,
|
def init_callbacks(tox, profile, settings, plugin_loader, contacts_manager,
|
||||||
|
@ -2,12 +2,12 @@
|
|||||||
import sys
|
import sys
|
||||||
import threading
|
import threading
|
||||||
import queue
|
import queue
|
||||||
from PyQt5 import QtCore
|
from qtpy import QtCore
|
||||||
|
|
||||||
from bootstrap.bootstrap import *
|
from bootstrap.bootstrap import *
|
||||||
from bootstrap.bootstrap import download_nodes_list
|
from bootstrap.bootstrap import download_nodes_list
|
||||||
from wrapper.toxcore_enums_and_consts import TOX_USER_STATUS, TOX_CONNECTION
|
from toxygen_wrapper.toxcore_enums_and_consts import TOX_USER_STATUS, TOX_CONNECTION
|
||||||
import wrapper_tests.support_testing as ts
|
import toxygen_wrapper.tests.support_testing as ts
|
||||||
from utils import util
|
from utils import util
|
||||||
|
|
||||||
import time
|
import time
|
||||||
@ -23,14 +23,12 @@ def LOG_ERROR(l): print('EROR+ '+l)
|
|||||||
def LOG_WARN(l): print('WARN+ '+l)
|
def LOG_WARN(l): print('WARN+ '+l)
|
||||||
def LOG_INFO(l): print('INFO+ '+l)
|
def LOG_INFO(l): print('INFO+ '+l)
|
||||||
def LOG_DEBUG(l): print('DBUG+ '+l)
|
def LOG_DEBUG(l): print('DBUG+ '+l)
|
||||||
def LOG_TRACE(l): pass # print('TRACE+ '+l)
|
def LOG_TRACE(l): pass # print('TRAC+ '+l)
|
||||||
|
|
||||||
iLAST_CONN = 0
|
iLAST_CONN = 0
|
||||||
iLAST_DELTA = 60
|
iLAST_DELTA = 60
|
||||||
|
|
||||||
# -----------------------------------------------------------------------------------------------------------------
|
|
||||||
# Base threads
|
# Base threads
|
||||||
# -----------------------------------------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
class BaseThread(threading.Thread):
|
class BaseThread(threading.Thread):
|
||||||
|
|
||||||
@ -74,9 +72,7 @@ class BaseQThread(QtCore.QThread):
|
|||||||
else:
|
else:
|
||||||
LOG_WARN(f"BaseQThread {self.name} BLOCKED")
|
LOG_WARN(f"BaseQThread {self.name} BLOCKED")
|
||||||
|
|
||||||
# -----------------------------------------------------------------------------------------------------------------
|
|
||||||
# Toxcore threads
|
# Toxcore threads
|
||||||
# -----------------------------------------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
class InitThread(BaseThread):
|
class InitThread(BaseThread):
|
||||||
|
|
||||||
@ -90,7 +86,7 @@ class InitThread(BaseThread):
|
|||||||
|
|
||||||
def run(self):
|
def run(self):
|
||||||
# DBUG+ InitThread run: ERROR name 'ts' is not defined
|
# DBUG+ InitThread run: ERROR name 'ts' is not defined
|
||||||
import wrapper_tests.support_testing as ts
|
import toxygen_wrapper.tests.support_testing as ts
|
||||||
LOG_DEBUG('InitThread run: ')
|
LOG_DEBUG('InitThread run: ')
|
||||||
try:
|
try:
|
||||||
if self._is_first_start and ts.bAreWeConnected() and \
|
if self._is_first_start and ts.bAreWeConnected() and \
|
||||||
@ -163,9 +159,7 @@ class ToxAVIterateThread(BaseQThread):
|
|||||||
sleep(self._toxav.iteration_interval() / 1000)
|
sleep(self._toxav.iteration_interval() / 1000)
|
||||||
|
|
||||||
|
|
||||||
# -----------------------------------------------------------------------------------------------------------------
|
|
||||||
# File transfers thread
|
# File transfers thread
|
||||||
# -----------------------------------------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
class FileTransfersThread(BaseQThread):
|
class FileTransfersThread(BaseQThread):
|
||||||
|
|
||||||
@ -203,9 +197,7 @@ def execute(func, *args, **kwargs):
|
|||||||
_thread.execute(func, *args, **kwargs)
|
_thread.execute(func, *args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
# -----------------------------------------------------------------------------------------------------------------
|
|
||||||
# Invoking in main thread
|
# Invoking in main thread
|
||||||
# -----------------------------------------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
class InvokeEvent(QtCore.QEvent):
|
class InvokeEvent(QtCore.QEvent):
|
||||||
EVENT_TYPE = QtCore.QEvent.Type(QtCore.QEvent.registerEventType())
|
EVENT_TYPE = QtCore.QEvent.Type(QtCore.QEvent.registerEventType())
|
||||||
|
@ -1,59 +1,25 @@
|
|||||||
# -*- mode: python; indent-tabs-mode: nil; py-indent-offset: 4; coding: utf-8 -*-
|
# -*- mode: python; indent-tabs-mode: nil; py-indent-offset: 4; coding: utf-8 -*-
|
||||||
import user_data.settings
|
|
||||||
import wrapper.tox
|
|
||||||
import wrapper.toxcore_enums_and_consts as enums
|
|
||||||
import ctypes
|
import ctypes
|
||||||
import traceback
|
import traceback
|
||||||
import os
|
import os
|
||||||
|
from ctypes import *
|
||||||
|
|
||||||
|
import user_data.settings
|
||||||
|
import toxygen_wrapper.tox
|
||||||
|
import toxygen_wrapper.toxcore_enums_and_consts as enums
|
||||||
|
from toxygen_wrapper.tests import support_testing as ts
|
||||||
|
# callbacks can be called in any thread so were being careful
|
||||||
|
# tox.py can be called by callbacks
|
||||||
|
from toxygen_wrapper.tests.support_testing import LOG_ERROR, LOG_WARN, LOG_INFO, LOG_DEBUG, LOG_TRACE
|
||||||
|
|
||||||
global LOG
|
global LOG
|
||||||
import logging
|
import logging
|
||||||
LOG = logging.getLogger('app.'+'tox_factory')
|
LOG = logging.getLogger('app.'+'tox_factory')
|
||||||
|
|
||||||
from ctypes import *
|
|
||||||
from utils import util
|
from utils import util
|
||||||
from utils import ui as util_ui
|
from utils import ui as util_ui
|
||||||
|
|
||||||
# callbacks can be called in any thread so were being careful
|
|
||||||
# tox.py can be called by callbacks
|
|
||||||
def LOG_ERROR(a): print('EROR> '+a)
|
|
||||||
def LOG_WARN(a): print('WARN> '+a)
|
|
||||||
def LOG_INFO(a):
|
|
||||||
bVERBOSE = hasattr(__builtins__, 'app') and app.oArgs.loglevel <= 20
|
|
||||||
if bVERBOSE: print('INFO> '+a)
|
|
||||||
def LOG_DEBUG(a):
|
|
||||||
bVERBOSE = hasattr(__builtins__, 'app') and app.oArgs.loglevel <= 10
|
|
||||||
if bVERBOSE: print('DBUG> '+a)
|
|
||||||
def LOG_TRACE(a):
|
|
||||||
bVERBOSE = hasattr(__builtins__, 'app') and app.oArgs.loglevel < 10
|
|
||||||
if bVERBOSE: print('TRAC> '+a)
|
|
||||||
def LOG_LOG(a): print('TRAC> '+a)
|
|
||||||
|
|
||||||
def tox_log_cb(iTox, level, file, line, func, message, *args):
|
|
||||||
"""
|
|
||||||
* @param level The severity of the log message.
|
|
||||||
* @param file 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.
|
|
||||||
"""
|
|
||||||
try:
|
|
||||||
file = str(file, 'UTF-8')
|
|
||||||
# root WARNING 3network.c#944:b'send_packet'attempted to send message with network family 10 (probably IPv6) on IPv4 socket
|
|
||||||
if file == 'network.c' and line in [944, 660]: return
|
|
||||||
func = str(func, 'UTF-8')
|
|
||||||
message = str(message, 'UTF-8')
|
|
||||||
message = f"{file}#{line}:{func} {message}"
|
|
||||||
LOG_LOG(message)
|
|
||||||
except Exception as e:
|
|
||||||
LOG_ERROR(f"tox_log_cb {e}")
|
|
||||||
|
|
||||||
#tox_log_handler (context=0x24763d0,
|
|
||||||
# level=LOGGER_LEVEL_TRACE, file=0x7fffe599fb99 "TCP_common.c", line=203,
|
|
||||||
# func=0x7fffe599fc50 <__func__.2> "read_TCP_packet",
|
|
||||||
# message=0x7fffba7fabd0 "recv buffer has 0 bytes, but requested 10 bytes",
|
|
||||||
# userdata=0x0) at /var/local/src/c-toxcore/toxcore/tox.c:78
|
|
||||||
|
|
||||||
def tox_factory(data=None, settings=None, args=None, app=None):
|
def tox_factory(data=None, settings=None, args=None, app=None):
|
||||||
"""
|
"""
|
||||||
@ -68,7 +34,7 @@ def tox_factory(data=None, settings=None, args=None, app=None):
|
|||||||
user_data.settings.clean_settings(settings)
|
user_data.settings.clean_settings(settings)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
tox_options = wrapper.tox.Tox.options_new()
|
tox_options = toxygen_wrapper.tox.Tox.options_new()
|
||||||
tox_options.contents.ipv6_enabled = settings['ipv6_enabled']
|
tox_options.contents.ipv6_enabled = settings['ipv6_enabled']
|
||||||
tox_options.contents.udp_enabled = settings['udp_enabled']
|
tox_options.contents.udp_enabled = settings['udp_enabled']
|
||||||
tox_options.contents.proxy_type = int(settings['proxy_type'])
|
tox_options.contents.proxy_type = int(settings['proxy_type'])
|
||||||
@ -99,27 +65,25 @@ def tox_factory(data=None, settings=None, args=None, app=None):
|
|||||||
tox_options.contents.ipv6_enabled = False
|
tox_options.contents.ipv6_enabled = False
|
||||||
tox_options.contents.hole_punching_enabled = False
|
tox_options.contents.hole_punching_enabled = False
|
||||||
|
|
||||||
LOG.debug("wrapper.tox.Tox settings: " +repr(settings))
|
LOG.debug("toxygen_wrapper.tox.Tox settings: " +repr(settings))
|
||||||
|
|
||||||
if 'trace_enabled' in settings and settings['trace_enabled']:
|
if 'trace_enabled' in settings and not settings['trace_enabled']:
|
||||||
LOG_INFO("settings['trace_enabled' disabled" )
|
LOG_DEBUG("settings['trace_enabled' disabled" )
|
||||||
elif tox_options._options_pointer:
|
elif tox_options._options_pointer and \
|
||||||
c_callback = CFUNCTYPE(None, c_void_p, c_int, c_char_p, c_int, c_char_p, c_char_p, c_void_p)
|
'trace_enabled' in settings and settings['trace_enabled']:
|
||||||
tox_options.self_logger_cb = c_callback(tox_log_cb)
|
ts.vAddLoggerCallback(tox_options)
|
||||||
wrapper.tox.Tox.libtoxcore.tox_options_set_log_callback(
|
LOG_INFO("c-toxcore trace_enabled enabled" )
|
||||||
tox_options._options_pointer,
|
|
||||||
tox_options.self_logger_cb)
|
|
||||||
else:
|
else:
|
||||||
LOG_WARN("No tox_options._options_pointer to add self_logger_cb" )
|
LOG_WARN("No tox_options._options_pointer to add self_logger_cb" )
|
||||||
|
|
||||||
retval = wrapper.tox.Tox(tox_options)
|
retval = toxygen_wrapper.tox.Tox(tox_options)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
if app and hasattr(app, '_log'):
|
if app and hasattr(app, '_log'):
|
||||||
pass
|
pass
|
||||||
LOG_ERROR(f"wrapper.tox.Tox failed: {e}")
|
LOG_ERROR(f"toxygen_wrapper.tox.Tox failed: {e}")
|
||||||
LOG_WARN(traceback.format_exc())
|
LOG_WARN(traceback.format_exc())
|
||||||
raise
|
raise
|
||||||
|
|
||||||
if app and hasattr(app, '_log'):
|
if app and hasattr(app, '_log'):
|
||||||
app._log("DEBUG: wrapper.tox.Tox succeeded")
|
app._log("DEBUG: toxygen_wrapper.tox.Tox succeeded")
|
||||||
return retval
|
return retval
|
||||||
|
@ -1,15 +1,19 @@
|
|||||||
# -*- mode: python; indent-tabs-mode: nil; py-indent-offset: 4; coding: utf-8 -*-
|
# -*- mode: python; indent-tabs-mode: nil; py-indent-offset: 4; coding: utf-8 -*-
|
||||||
import json
|
import json
|
||||||
import urllib.request
|
import urllib.request
|
||||||
import utils.util as util
|
import logging
|
||||||
from PyQt5 import QtNetwork, QtCore
|
|
||||||
try:
|
try:
|
||||||
import requests
|
import requests
|
||||||
except ImportError:
|
except ImportError:
|
||||||
requests = None
|
requests = None
|
||||||
|
from qtpy import QtNetwork, QtCore
|
||||||
|
|
||||||
|
import utils.util as util
|
||||||
|
|
||||||
global LOG
|
global LOG
|
||||||
import logging
|
|
||||||
|
iTIMEOUT=60
|
||||||
LOG = logging.getLogger('app.'+__name__)
|
LOG = logging.getLogger('app.'+__name__)
|
||||||
|
|
||||||
class ToxDns:
|
class ToxDns:
|
||||||
@ -55,7 +59,7 @@ class ToxDns:
|
|||||||
try:
|
try:
|
||||||
headers = dict()
|
headers = dict()
|
||||||
headers['Content-Type'] = 'application/json'
|
headers['Content-Type'] = 'application/json'
|
||||||
req = requests.get(url, headers=headers)
|
req = requests.get(url, headers=headers, timeout=iTIMEOUT)
|
||||||
if req.status_code < 300:
|
if req.status_code < 300:
|
||||||
result = req.content
|
result = req.content
|
||||||
return result
|
return result
|
||||||
|
@ -1,7 +1,13 @@
|
|||||||
import utils.util
|
# -*- mode: python; indent-tabs-mode: nil; py-indent-offset: 4; coding: utf-8 -*-
|
||||||
import wave
|
|
||||||
import pyaudio
|
|
||||||
import os.path
|
import os.path
|
||||||
|
import wave
|
||||||
|
|
||||||
|
import utils.util
|
||||||
|
|
||||||
|
import toxygen_wrapper.tests.support_testing as ts
|
||||||
|
with ts.ignoreStderr():
|
||||||
|
import pyaudio
|
||||||
|
|
||||||
global LOG
|
global LOG
|
||||||
import logging
|
import logging
|
||||||
@ -33,7 +39,7 @@ class AudioFile:
|
|||||||
self.stream.write(data)
|
self.stream.write(data)
|
||||||
data = self.wf.readframes(self.chunk)
|
data = self.wf.readframes(self.chunk)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
LOG.error(f"Error during AudioFile play {e!s}")
|
LOG.error(f"Error during AudioFile play {e}")
|
||||||
LOG.debug("Error during AudioFile play " \
|
LOG.debug("Error during AudioFile play " \
|
||||||
+' rate=' +str(self.wf.getframerate()) \
|
+' rate=' +str(self.wf.getframerate()) \
|
||||||
+ 'format=' +str(self.p.get_format_from_width(self.wf.getsampwidth())) \
|
+ 'format=' +str(self.p.get_format_from_width(self.wf.getsampwidth())) \
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
from PyQt5 import QtCore, QtWidgets
|
# -*- mode: python; indent-tabs-mode: nil; py-indent-offset: 4; coding: utf-8 -*-
|
||||||
|
|
||||||
|
from qtpy import QtCore, QtWidgets
|
||||||
|
|
||||||
def tray_notification(title, text, tray, window):
|
def tray_notification(title, text, tray, window):
|
||||||
"""
|
"""
|
||||||
|
@ -1,14 +1,15 @@
|
|||||||
# -*- mode: python; indent-tabs-mode: nil; py-indent-offset: 4; coding: utf-8 -*-
|
# -*- mode: python; indent-tabs-mode: nil; py-indent-offset: 4; coding: utf-8 -*-
|
||||||
import utils.util as util
|
|
||||||
import os
|
import os
|
||||||
import importlib
|
import importlib
|
||||||
import inspect
|
import inspect
|
||||||
import plugins.plugin_super_class as pl
|
|
||||||
import sys
|
import sys
|
||||||
|
import logging
|
||||||
|
|
||||||
|
import utils.util as util
|
||||||
|
import plugins.plugin_super_class as pl
|
||||||
|
|
||||||
# LOG=util.log
|
# LOG=util.log
|
||||||
global LOG
|
global LOG
|
||||||
import logging
|
|
||||||
LOG = logging.getLogger('plugin_support')
|
LOG = logging.getLogger('plugin_support')
|
||||||
def trace(msg, *args, **kwargs): LOG._log(0, msg, [])
|
def trace(msg, *args, **kwargs): LOG._log(0, msg, [])
|
||||||
LOG.trace = trace
|
LOG.trace = trace
|
||||||
@ -42,14 +43,14 @@ class PluginLoader:
|
|||||||
self._app = app
|
self._app = app
|
||||||
self._plugins = {} # dict. key - plugin unique short name, value - Plugin instance
|
self._plugins = {} # dict. key - plugin unique short name, value - Plugin instance
|
||||||
|
|
||||||
def set_tox(self, tox):
|
def set_tox(self, tox) -> None:
|
||||||
"""
|
"""
|
||||||
New tox instance
|
New tox instance
|
||||||
"""
|
"""
|
||||||
for plugin in self._plugins.values():
|
for plugin in self._plugins.values():
|
||||||
plugin.instance.set_tox(tox)
|
plugin.instance.set_tox(tox)
|
||||||
|
|
||||||
def load(self):
|
def load(self) -> None:
|
||||||
"""
|
"""
|
||||||
Load all plugins in plugins folder
|
Load all plugins in plugins folder
|
||||||
"""
|
"""
|
||||||
@ -88,9 +89,9 @@ class PluginLoader:
|
|||||||
if is_active:
|
if is_active:
|
||||||
try:
|
try:
|
||||||
instance.start()
|
instance.start()
|
||||||
self._app.LOG('INFO: Started Plugin ' +short_name)
|
self._app._log('INFO: Started Plugin ' +short_name)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self._app.LOG.error(f"Starting Plugin ' +short_name +' {e}")
|
self._app._log.error(f"Starting Plugin ' +short_name +' {e}")
|
||||||
# else: LOG.info('Defined Plugin ' +short_name)
|
# else: LOG.info('Defined Plugin ' +short_name)
|
||||||
except Exception as ex:
|
except Exception as ex:
|
||||||
LOG.error('in module ' + short_name + ' Exception: ' + str(ex))
|
LOG.error('in module ' + short_name + ' Exception: ' + str(ex))
|
||||||
@ -100,7 +101,7 @@ class PluginLoader:
|
|||||||
LOG.info('Added plugin: ' +short_name +' from file: ' +fl)
|
LOG.info('Added plugin: ' +short_name +' from file: ' +fl)
|
||||||
break
|
break
|
||||||
|
|
||||||
def callback_lossless(self, friend_number, data):
|
def callback_lossless(self, friend_number, data) -> None:
|
||||||
"""
|
"""
|
||||||
New incoming custom lossless packet (callback)
|
New incoming custom lossless packet (callback)
|
||||||
"""
|
"""
|
||||||
@ -118,7 +119,7 @@ class PluginLoader:
|
|||||||
if name in self._plugins and self._plugins[name].is_active:
|
if name in self._plugins and self._plugins[name].is_active:
|
||||||
self._plugins[name].instance.lossy_packet(''.join(chr(x) for x in data[l + 1:]), friend_number)
|
self._plugins[name].instance.lossy_packet(''.join(chr(x) for x in data[l + 1:]), friend_number)
|
||||||
|
|
||||||
def friend_online(self, friend_number):
|
def friend_online(self, friend_number:int) -> None:
|
||||||
"""
|
"""
|
||||||
Friend with specified number is online
|
Friend with specified number is online
|
||||||
"""
|
"""
|
||||||
@ -126,7 +127,7 @@ class PluginLoader:
|
|||||||
if plugin.is_active:
|
if plugin.is_active:
|
||||||
plugin.instance.friend_connected(friend_number)
|
plugin.instance.friend_connected(friend_number)
|
||||||
|
|
||||||
def get_plugins_list(self):
|
def get_plugins_list(self) -> list:
|
||||||
"""
|
"""
|
||||||
Returns list of all plugins
|
Returns list of all plugins
|
||||||
"""
|
"""
|
||||||
@ -150,11 +151,11 @@ class PluginLoader:
|
|||||||
if key in self._plugins and hasattr(self._plugins[key], 'instance'):
|
if key in self._plugins and hasattr(self._plugins[key], 'instance'):
|
||||||
return self._plugins[key].instance.get_window()
|
return self._plugins[key].instance.get_window()
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self._app.LOG('WARN: ' +key +' _plugins no slot instance: ' +str(e))
|
self._app._log('WARN: ' +key +' _plugins no slot instance: ' +str(e))
|
||||||
|
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def toggle_plugin(self, key):
|
def toggle_plugin(self, key) -> None:
|
||||||
"""
|
"""
|
||||||
Enable/disable plugin
|
Enable/disable plugin
|
||||||
:param key: plugin short name
|
:param key: plugin short name
|
||||||
@ -171,7 +172,7 @@ class PluginLoader:
|
|||||||
self._settings['plugins'].remove(key)
|
self._settings['plugins'].remove(key)
|
||||||
self._settings.save()
|
self._settings.save()
|
||||||
|
|
||||||
def command(self, text):
|
def command(self, text) -> None:
|
||||||
"""
|
"""
|
||||||
New command for plugin
|
New command for plugin
|
||||||
"""
|
"""
|
||||||
@ -202,7 +203,7 @@ class PluginLoader:
|
|||||||
continue
|
continue
|
||||||
if not hasattr(plugin.instance, 'get_message_menu'):
|
if not hasattr(plugin.instance, 'get_message_menu'):
|
||||||
name = plugin.instance.get_short_name()
|
name = plugin.instance.get_short_name()
|
||||||
self._app.LOG('WARN: get_message_menu not found: ' + name)
|
self._app._log('WARN: get_message_menu not found: ' + name)
|
||||||
continue
|
continue
|
||||||
try:
|
try:
|
||||||
result.extend(plugin.instance.get_message_menu(menu, selected_text))
|
result.extend(plugin.instance.get_message_menu(menu, selected_text))
|
||||||
@ -210,7 +211,7 @@ class PluginLoader:
|
|||||||
pass
|
pass
|
||||||
return result
|
return result
|
||||||
|
|
||||||
def stop(self):
|
def stop(self) -> None:
|
||||||
"""
|
"""
|
||||||
App is closing, stop all plugins
|
App is closing, stop all plugins
|
||||||
"""
|
"""
|
||||||
@ -219,12 +220,12 @@ class PluginLoader:
|
|||||||
self._plugins[key].instance.close()
|
self._plugins[key].instance.close()
|
||||||
del self._plugins[key]
|
del self._plugins[key]
|
||||||
|
|
||||||
def reload(self):
|
def reload(self) -> None:
|
||||||
path = util.get_plugins_directory()
|
path = util.get_plugins_directory()
|
||||||
if not os.path.exists(path):
|
if not os.path.exists(path):
|
||||||
self._app.LOG('WARN: Plugin directory not found: ' + path)
|
self._app._log('WARN: Plugin directory not found: ' + path)
|
||||||
return
|
return
|
||||||
|
|
||||||
self.stop()
|
self.stop()
|
||||||
self._app.LOG('INFO: Reloading plugins from ' +path)
|
self._app._log('INFO: Reloading plugins from ' +path)
|
||||||
self.load()
|
self.load()
|
||||||
|
27
toxygen/plugins/README.md
Normal file
27
toxygen/plugins/README.md
Normal 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!
|
85
toxygen/plugins/ae.py
Normal file
85
toxygen/plugins/ae.py
Normal file
@ -0,0 +1,85 @@
|
|||||||
|
# -*- mode: python; indent-tabs-mode: nil; py-indent-offset: 4; coding: utf-8 -*-
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
from qtpy import QtWidgets
|
||||||
|
|
||||||
|
from bootstrap.bootstrap import get_user_config_path
|
||||||
|
from user_data import settings
|
||||||
|
import plugin_super_class
|
||||||
|
|
||||||
|
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 QtWidgets.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
|
114
toxygen/plugins/awayl.py
Normal file
114
toxygen/plugins/awayl.py
Normal file
@ -0,0 +1,114 @@
|
|||||||
|
import plugin_super_class
|
||||||
|
import threading
|
||||||
|
# -*- mode: python; indent-tabs-mode: nil; py-indent-offset: 4; coding: utf-8 -*-
|
||||||
|
|
||||||
|
import json
|
||||||
|
from subprocess import check_output
|
||||||
|
import time
|
||||||
|
|
||||||
|
from qtpy import QtCore, QtWidgets
|
||||||
|
|
||||||
|
|
||||||
|
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)
|
115
toxygen/plugins/awayw.py.windows
Normal file
115
toxygen/plugins/awayw.py.windows
Normal 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
2
toxygen/plugins/bday.pro
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
SOURCES = bday.py
|
||||||
|
TRANSLATIONS = bday/en_GB.ts bday/en_US.ts bday/ru_RU.ts
|
98
toxygen/plugins/bday.py
Normal file
98
toxygen/plugins/bday.py
Normal file
@ -0,0 +1,98 @@
|
|||||||
|
# -*- mode: python; indent-tabs-mode: nil; py-indent-offset: 4; coding: utf-8 -*-
|
||||||
|
|
||||||
|
import json
|
||||||
|
import importlib
|
||||||
|
|
||||||
|
from qtpy import QtWidgets, QtCore
|
||||||
|
|
||||||
|
import plugin_super_class
|
||||||
|
|
||||||
|
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) -> None:
|
||||||
|
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 QtWidgets.QApplication.translate("BirthDay", "Send and get notifications on your friends' birthdays.")
|
||||||
|
|
||||||
|
def get_window(self) -> None:
|
||||||
|
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) -> None:
|
||||||
|
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:int) -> None:
|
||||||
|
timer = QtCore.QTimer()
|
||||||
|
timer.timeout.connect(lambda: self.timer(friend_number))
|
||||||
|
timer.start(10000)
|
||||||
|
self._timers.append(timer)
|
||||||
|
|
||||||
|
def timer(self, friend_number:int) -> None:
|
||||||
|
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)
|
||||||
|
|
83
toxygen/plugins/bot.py
Normal file
83
toxygen/plugins/bot.py
Normal file
@ -0,0 +1,83 @@
|
|||||||
|
import time
|
||||||
|
|
||||||
|
from qtpy import QtCore, QtWidgets
|
||||||
|
|
||||||
|
import plugin_super_class
|
||||||
|
|
||||||
|
|
||||||
|
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 QtWidgets.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)
|
||||||
|
|
1696
toxygen/plugins/chess.py
Normal file
1696
toxygen/plugins/chess.py
Normal file
File diff suppressed because it is too large
Load Diff
31
toxygen/plugins/en_GB.ts
Normal file
31
toxygen/plugins/en_GB.ts
Normal 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
31
toxygen/plugins/en_US.ts
Normal 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>
|
78
toxygen/plugins/garland.py
Normal file
78
toxygen/plugins/garland.py
Normal file
@ -0,0 +1,78 @@
|
|||||||
|
# -*- mode: python; indent-tabs-mode: nil; py-indent-offset: 4; coding: utf-8 -*-
|
||||||
|
|
||||||
|
import threading
|
||||||
|
import time
|
||||||
|
|
||||||
|
from qtpy import QtCore, QtWidgets
|
||||||
|
|
||||||
|
from plugins.plugin_super_class import PluginSuperClass
|
||||||
|
|
||||||
|
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(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 QtWidgets.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)
|
||||||
|
|
87
toxygen/plugins/mrq.py
Normal file
87
toxygen/plugins/mrq.py
Normal file
@ -0,0 +1,87 @@
|
|||||||
|
# -*- mode: python; indent-tabs-mode: nil; py-indent-offset: 4; coding: utf-8 -*-
|
||||||
|
import threading
|
||||||
|
import time
|
||||||
|
|
||||||
|
from qtpy import QtCore, QtWidgets
|
||||||
|
|
||||||
|
import plugin_super_class
|
||||||
|
|
||||||
|
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 QtWidgets.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
|
||||||
|
|
@ -1,9 +1,10 @@
|
|||||||
|
# -*- mode: python; indent-tabs-mode: nil; py-indent-offset: 4; coding: utf-8 -*-
|
||||||
import os
|
import os
|
||||||
|
|
||||||
from PyQt5 import QtCore, QtWidgets
|
from qtpy import QtCore, QtWidgets
|
||||||
|
|
||||||
import common.tox_save as tox_save
|
|
||||||
import utils.ui as util_ui
|
import utils.ui as util_ui
|
||||||
|
import common.tox_save as tox_save
|
||||||
|
|
||||||
MAX_SHORT_NAME_LENGTH = 5
|
MAX_SHORT_NAME_LENGTH = 5
|
||||||
|
|
||||||
@ -11,7 +12,6 @@ LOSSY_FIRST_BYTE = 200
|
|||||||
|
|
||||||
LOSSLESS_FIRST_BYTE = 160
|
LOSSLESS_FIRST_BYTE = 160
|
||||||
|
|
||||||
|
|
||||||
def path_to_data(name):
|
def path_to_data(name):
|
||||||
"""
|
"""
|
||||||
:param name: plugin unique name
|
:param name: plugin unique name
|
||||||
@ -20,7 +20,7 @@ def path_to_data(name):
|
|||||||
return os.path.dirname(os.path.realpath(__file__)) + '/' + name + '/'
|
return os.path.dirname(os.path.realpath(__file__)) + '/' + name + '/'
|
||||||
|
|
||||||
|
|
||||||
def log(name, data):
|
def log(name, data=''):
|
||||||
"""
|
"""
|
||||||
:param name: plugin unique name
|
:param name: plugin unique name
|
||||||
:param data: data for saving in log
|
:param data: data for saving in log
|
||||||
@ -48,14 +48,12 @@ class PluginSuperClass(tox_save.ToxSave):
|
|||||||
name = name.strip()
|
name = name.strip()
|
||||||
short_name = short_name.strip()
|
short_name = short_name.strip()
|
||||||
if not name or not short_name:
|
if not name or not short_name:
|
||||||
raise NameError('Wrong name')
|
raise NameError('Wrong name or not name or not short_name')
|
||||||
self._name = name
|
self._name = name
|
||||||
self._short_name = short_name[:MAX_SHORT_NAME_LENGTH]
|
self._short_name = short_name[:MAX_SHORT_NAME_LENGTH]
|
||||||
self._translator = None # translator for plugin's GUI
|
self._translator = None # translator for plugin's GUI
|
||||||
|
|
||||||
# -----------------------------------------------------------------------------------------------------------------
|
|
||||||
# Get methods
|
# Get methods
|
||||||
# -----------------------------------------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
def get_name(self):
|
def get_name(self):
|
||||||
"""
|
"""
|
||||||
@ -75,7 +73,7 @@ class PluginSuperClass(tox_save.ToxSave):
|
|||||||
"""
|
"""
|
||||||
return self.__doc__
|
return self.__doc__
|
||||||
|
|
||||||
def get_menu(self, row_number):
|
def get_menu(self, menu, row_number=None):
|
||||||
"""
|
"""
|
||||||
This method creates items for menu which called on right click in list of friends
|
This method creates items for menu which called on right click in list of friends
|
||||||
:param row_number: number of selected row in list of contacts
|
:param row_number: number of selected row in list of contacts
|
||||||
@ -98,9 +96,7 @@ class PluginSuperClass(tox_save.ToxSave):
|
|||||||
"""
|
"""
|
||||||
return None
|
return None
|
||||||
|
|
||||||
# -----------------------------------------------------------------------------------------------------------------
|
|
||||||
# Plugin was stopped, started or new command received
|
# Plugin was stopped, started or new command received
|
||||||
# -----------------------------------------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
def start(self):
|
def start(self):
|
||||||
"""
|
"""
|
||||||
@ -130,9 +126,7 @@ class PluginSuperClass(tox_save.ToxSave):
|
|||||||
title = util_ui.tr('List of commands for plugin {}').format(self._name)
|
title = util_ui.tr('List of commands for plugin {}').format(self._name)
|
||||||
util_ui.message_box(text, title)
|
util_ui.message_box(text, title)
|
||||||
|
|
||||||
# -----------------------------------------------------------------------------------------------------------------
|
|
||||||
# Translations support
|
# Translations support
|
||||||
# -----------------------------------------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
def load_translator(self):
|
def load_translator(self):
|
||||||
"""
|
"""
|
||||||
@ -149,9 +143,7 @@ class PluginSuperClass(tox_save.ToxSave):
|
|||||||
self._translator.load(path_to_data(self._short_name) + lang_path)
|
self._translator.load(path_to_data(self._short_name) + lang_path)
|
||||||
app.installTranslator(self._translator)
|
app.installTranslator(self._translator)
|
||||||
|
|
||||||
# -----------------------------------------------------------------------------------------------------------------
|
|
||||||
# Settings loading and saving
|
# Settings loading and saving
|
||||||
# -----------------------------------------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
def load_settings(self):
|
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:
|
with open(path_to_data(self._short_name) + 'settings.json', 'wb') as fl:
|
||||||
fl.write(bytes(data, 'utf-8'))
|
fl.write(bytes(data, 'utf-8'))
|
||||||
|
|
||||||
# -----------------------------------------------------------------------------------------------------------------
|
|
||||||
# Callbacks
|
# Callbacks
|
||||||
# -----------------------------------------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
def lossless_packet(self, data, friend_number):
|
def lossless_packet(self, data, friend_number):
|
||||||
"""
|
"""
|
||||||
@ -190,15 +180,13 @@ class PluginSuperClass(tox_save.ToxSave):
|
|||||||
"""
|
"""
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def friend_connected(self, friend_number):
|
def friend_connected(self, friend_number:int):
|
||||||
"""
|
"""
|
||||||
Friend with specified number is online now
|
Friend with specified number is online now
|
||||||
"""
|
"""
|
||||||
pass
|
pass
|
||||||
|
|
||||||
# -----------------------------------------------------------------------------------------------------------------
|
|
||||||
# Custom packets sending
|
# Custom packets sending
|
||||||
# -----------------------------------------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
def send_lossless(self, data, friend_number):
|
def send_lossless(self, data, friend_number):
|
||||||
"""
|
"""
|
||||||
|
BIN
toxygen/plugins/ru_RU.qm
Normal file
BIN
toxygen/plugins/ru_RU.qm
Normal file
Binary file not shown.
32
toxygen/plugins/ru_RU.ts
Normal file
32
toxygen/plugins/ru_RU.ts
Normal 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
2
toxygen/plugins/srch.pro
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
SOURCES = srch.py
|
||||||
|
TRANSLATIONS = srch/en_GB.ts srch/en_US.ts srch/ru_RU.ts
|
56
toxygen/plugins/srch.py
Normal file
56
toxygen/plugins/srch.py
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
# -*- mode: python; indent-tabs-mode: nil; py-indent-offset: 4; coding: utf-8 -*-
|
||||||
|
|
||||||
|
from qtpy import QtGui, QtCore, QtWidgets
|
||||||
|
|
||||||
|
import plugin_super_class
|
||||||
|
|
||||||
|
class SearchPlugin(plugin_super_class.PluginSuperClass):
|
||||||
|
|
||||||
|
def __init__(self, *args):
|
||||||
|
super(SearchPlugin, self).__init__('SearchPlugin', 'srch', *args)
|
||||||
|
|
||||||
|
def get_description(self):
|
||||||
|
return QtWidgets.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)
|
||||||
|
|
2
toxygen/plugins/toxid.pro
Normal file
2
toxygen/plugins/toxid.pro
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
SOURCES = toxid.py
|
||||||
|
TRANSLATIONS = toxid/en_GB.ts toxid/en_US.ts toxid/ru_RU.ts
|
140
toxygen/plugins/toxid.py
Normal file
140
toxygen/plugins/toxid.py
Normal file
@ -0,0 +1,140 @@
|
|||||||
|
# -*- mode: python; indent-tabs-mode: nil; py-indent-offset: 4; coding: utf-8 -*-
|
||||||
|
|
||||||
|
import json
|
||||||
|
|
||||||
|
from qtpy import QtCore, QtWidgets
|
||||||
|
|
||||||
|
from plugins.plugin_super_class import PluginSuperClass
|
||||||
|
|
||||||
|
class CopyableToxId(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) -> None:
|
||||||
|
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) -> None:
|
||||||
|
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) -> None:
|
||||||
|
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:int):
|
||||||
|
self.send_lossless('', friend_number)
|
||||||
|
|
||||||
|
def command(self, text) -> None:
|
||||||
|
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) -> list:
|
||||||
|
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]
|
@ -1,12 +1,16 @@
|
|||||||
from utils import util
|
# -*- mode: python; indent-tabs-mode: nil; py-indent-offset: 4; coding: utf-8 -*-
|
||||||
|
|
||||||
import json
|
import json
|
||||||
|
import logging
|
||||||
import os
|
import os
|
||||||
from collections import OrderedDict
|
from collections import OrderedDict
|
||||||
from PyQt5 import QtCore
|
|
||||||
|
from qtpy import QtCore
|
||||||
|
|
||||||
|
from utils import util
|
||||||
|
|
||||||
# LOG=util.log
|
# LOG=util.log
|
||||||
global LOG
|
global LOG
|
||||||
import logging
|
|
||||||
LOG = logging.getLogger('app.'+__name__)
|
LOG = logging.getLogger('app.'+__name__)
|
||||||
log = lambda x: LOG.info(x)
|
log = lambda x: LOG.info(x)
|
||||||
|
|
||||||
|
@ -1,7 +1,8 @@
|
|||||||
|
# -*- mode: python; indent-tabs-mode: nil; py-indent-offset: 4; coding: utf-8 -*-
|
||||||
|
|
||||||
import os
|
import os
|
||||||
import utils.util as util
|
import utils.util as util
|
||||||
|
|
||||||
|
|
||||||
def load_stickers():
|
def load_stickers():
|
||||||
"""
|
"""
|
||||||
:return list of stickers
|
:return list of stickers
|
||||||
|
File diff suppressed because one or more lines are too long
1
toxygen/tests/README.txt
Normal file
1
toxygen/tests/README.txt
Normal file
@ -0,0 +1 @@
|
|||||||
|
unused
|
0
toxygen/tests/__init__.py
Normal file
0
toxygen/tests/__init__.py
Normal file
151
toxygen/tests/conference_tests.py.bak
Normal file
151
toxygen/tests/conference_tests.py.bak
Normal 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
|
||||||
|
|
||||||
|
|
393
toxygen/tests/socks.py
Normal file
393
toxygen/tests/socks.py
Normal file
@ -0,0 +1,393 @@
|
|||||||
|
# -*- mode: python; indent-tabs-mode: nil; py-indent-offset: 4; coding: utf-8 -*-
|
||||||
|
|
||||||
|
"""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]))
|
938
toxygen/tests/test_gdb.py
Normal file
938
toxygen/tests/test_gdb.py
Normal file
@ -0,0 +1,938 @@
|
|||||||
|
# -*- mode: python; indent-tabs-mode: nil; py-indent-offset: 4; coding: utf-8 -*-
|
||||||
|
|
||||||
|
# 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__() # toxygen_wrapper_call()
|
||||||
|
|
||||||
|
id("first break point")
|
||||||
|
l = MyList()
|
||||||
|
''')
|
||||||
|
# Verify with "py-bt":
|
||||||
|
gdb_output = self.get_stack_trace(cmd,
|
||||||
|
cmds_after_breakpoint=['break toxygen_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()
|
1
toxygen/tests/test_gdb.urls
Normal file
1
toxygen/tests/test_gdb.urls
Normal file
@ -0,0 +1 @@
|
|||||||
|
https://github.com/akheron/cpython/raw/master/Lib/test/test_gdb.py
|
1885
toxygen/tests/tests_socks.py
Normal file
1885
toxygen/tests/tests_socks.py
Normal file
File diff suppressed because it is too large
Load Diff
0
toxygen/third_party/__init__.py
vendored
Normal file
0
toxygen/third_party/__init__.py
vendored
Normal file
41
toxygen/third_party/qweechat/data/icons/README
vendored
Normal file
41
toxygen/third_party/qweechat/data/icons/README
vendored
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
Copyright and license for images
|
||||||
|
================================
|
||||||
|
|
||||||
|
|
||||||
|
Files: weechat.png, bullet_green_8x8.png, bullet_yellow_8x8.png
|
||||||
|
|
||||||
|
Copyright (C) 2011-2022 Sébastien Helleu <flashcode@flashtux.org>
|
||||||
|
Released under GPLv3.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Files: application-exit.png, dialog-close.png, dialog-ok-apply.png,
|
||||||
|
dialog-password.png, dialog-warning.png, document-save.png,
|
||||||
|
edit-find.png, help-about.png, network-connect.png,
|
||||||
|
network-disconnect.png, preferences-other.png
|
||||||
|
|
||||||
|
Files come from Debian package "oxygen-icon-theme":
|
||||||
|
|
||||||
|
The Oxygen Icon Theme
|
||||||
|
Copyright (C) 2007 Nuno Pinheiro <nuno@oxygen-icons.org>
|
||||||
|
Copyright (C) 2007 David Vignoni <david@icon-king.com>
|
||||||
|
Copyright (C) 2007 David Miller <miller@oxygen-icons.org>
|
||||||
|
Copyright (C) 2007 Johann Ollivier Lapeyre <johann@oxygen-icons.org>
|
||||||
|
Copyright (C) 2007 Kenneth Wimer <kwwii@bootsplash.org>
|
||||||
|
Copyright (C) 2007 Riccardo Iaconelli <riccardo@oxygen-icons.org>
|
||||||
|
and others
|
||||||
|
|
||||||
|
License:
|
||||||
|
|
||||||
|
This library is free software; you can redistribute it and/or
|
||||||
|
modify it under the terms of the GNU Lesser General Public
|
||||||
|
License as published by the Free Software Foundation; either
|
||||||
|
version 3 of the License, or (at your option) any later version.
|
||||||
|
|
||||||
|
This library is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||||
|
Lesser General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU Lesser General Public
|
||||||
|
License along with this library. If not, see <http://www.gnu.org/licenses/>.
|
BIN
toxygen/third_party/qweechat/data/icons/application-exit.png
vendored
Normal file
BIN
toxygen/third_party/qweechat/data/icons/application-exit.png
vendored
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.7 KiB |
BIN
toxygen/third_party/qweechat/data/icons/bullet_green_8x8.png
vendored
Normal file
BIN
toxygen/third_party/qweechat/data/icons/bullet_green_8x8.png
vendored
Normal file
Binary file not shown.
After Width: | Height: | Size: 384 B |
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user