Compare commits
341 Commits
v0.1.3
...
0b1eaa1391
Author | SHA1 | Date | |
---|---|---|---|
0b1eaa1391 | |||
424e15b31c | |||
db37d29dc8 | |||
f1d8ce105c | |||
1e5618060a | |||
1b8b26eafc | |||
a073dd9bc9 | |||
5df00c3ccd | |||
0819fd4088 | |||
5f1b7d8d93 | |||
cf5c5b1608 | |||
90e379a6de | |||
a92bbbbcbf | |||
d2fe721072 | |||
fd7f2620ba | |||
b75aafe638 | |||
f7c0e7ce23 | |||
633b8f9561 | |||
fb520357e9 | |||
be6eb0e2a9 | |||
9e037f13c0 | |||
ca9c6fc091 | |||
2916d0cb04 | |||
695d8e2cf9 | |||
c5edc1f01b | |||
a7c07ffdf7 | |||
cdb0db5b4b | |||
a365b7d54c | |||
870e3125ad | |||
675bf1b2b9 | |||
cab3b4d9af | |||
9008bcdb7f | |||
61b926fe50 | |||
39f2638931 | |||
6f0c1a444e | |||
b51ec9bd71 | |||
fda07698db | |||
0a54012cf5 | |||
021ec52e3d | |||
5019535c0d | |||
1554d9e53a | |||
a984b624b5 | |||
2aea5df33c | |||
1fa13db4e4 | |||
3582722faa | |||
74396834cf | |||
ce84cc526b | |||
98cc288bcd | |||
9b5d768819 | |||
762eb89a46 | |||
b428bd54c4 | |||
f76a1c0fbe | |||
bb2a857ecf | |||
62c5df751d | |||
55a127a820 | |||
32055050ee | |||
a6633f1e77 | |||
23b55522ba | |||
5a5b0e9069 | |||
24c8b18f7e | |||
3ddb7470fc | |||
80b0ea4f0e | |||
6efb1790bb | |||
d5d1e616ba | |||
1ea919bdc2 | |||
65167de1fe | |||
db519e2608 | |||
19893c5c28 | |||
8e6d37e23c | |||
aae71d081f | |||
9c129e925b | |||
87392ea95a | |||
1bbd9a629c | |||
f4d806f5fc | |||
4854b6151d | |||
c755b4a52a | |||
7505b06ddf | |||
ace663804e | |||
2ff41313f8 | |||
1e1772e306 | |||
300b28bdfa | |||
1f4e81af35 | |||
335d646c42 | |||
b6f5123495 | |||
fbe0b1f819 | |||
000a4c7920 | |||
262714d3ee | |||
d06982b38a | |||
c21e39b158 | |||
8d0426f775 | |||
2c031fce3f | |||
6e1b8a9f17 | |||
4b85401adf | |||
5932d8cb84 | |||
adf6cefd1f | |||
142255ccc8 | |||
1b6b8e043a | |||
1b4c211c1d | |||
43c71ec1a5 | |||
4d4fd21fe9 | |||
6cbacef95b | |||
7a817eb82a | |||
49fc253c19 | |||
df5a1a901a | |||
54a2da4670 | |||
361f1f0e29 | |||
9031a4a3e3 | |||
0a378c1682 | |||
8bc4613407 | |||
ec6c04a7df | |||
769119c795 | |||
d1e90c6aef | |||
6d705deb55 | |||
464fba23c5 | |||
c60808a7da | |||
a2273e8c27 | |||
a20a00130d | |||
d0e2f61d03 | |||
8ea1a77186 | |||
bf1bea1e93 | |||
124decc34a | |||
89caef6905 | |||
9118e01775 | |||
138135b9e9 | |||
2863eb790d | |||
06e8c79b3f | |||
81695737cd | |||
ac07cb529f | |||
4f77e2c105 | |||
47ce9252b7 | |||
9153836ead | |||
f8a7087779 | |||
01546f0047 | |||
d8dd16e865 | |||
19fb905554 | |||
3ef581bc5d | |||
f897c7ce8d | |||
ba390eda91 | |||
5fea3e918d | |||
7cc404ce52 | |||
8a502b4082 | |||
b83ea6be18 | |||
85554eacd1 | |||
8bbefff6c7 | |||
019165aeac | |||
0cfb8efefa | |||
b227ed627a | |||
f41b5e5c97 | |||
bc9ec04171 | |||
05e4184c5d | |||
3194099f59 | |||
1a9db79ca2 | |||
21cc5837cf | |||
150942446d | |||
508db0acea | |||
de7f3359b8 | |||
8b56184510 | |||
1d33d298c3 | |||
704344fae2 | |||
3511031aff | |||
481e48f495 | |||
889d3d8f9c | |||
9b4965d591 | |||
5bdbb28e31 | |||
6cafd14883 | |||
9d939e7439 | |||
9e7e9b9012 | |||
2c4301e4f0 | |||
dc6ec7a6e8 | |||
6ae419441b | |||
1bdccf6f40 | |||
e854516183 | |||
5477a7d548 | |||
9f87f3dc3e | |||
137195c8f2 | |||
202c5a14a5 | |||
e598d027eb | |||
52a5d248c7 | |||
b0389537a1 | |||
34dd74ad48 | |||
e5a228906d | |||
3a90865fd0 | |||
b807daa3ff | |||
a83cd65f79 | |||
476f074d6a | |||
821dce5f28 | |||
67e9c92c09 | |||
9f745d9795 | |||
c4843148e4 | |||
56d8fa1cad | |||
1e6201b3fa | |||
ecf045182a | |||
5367764fdc | |||
417729d666 | |||
f782b99402 | |||
4c6205cc39 | |||
fd722f4628 | |||
dfab0491a5 | |||
8025c6a638 | |||
006b3cd197 | |||
9fe9ba4743 | |||
97ce2b9ceb | |||
337601f2a1 | |||
42e0ec005b | |||
fb1caa244a | |||
0fd75c5517 | |||
d81e3e781b | |||
43d9a41dae | |||
1caf7cd63c | |||
14816588f1 | |||
47b710acdd | |||
3668088f3e | |||
9f702afcb8 | |||
18775ff4b2 | |||
a7431cadd1 | |||
adcc32fc49 | |||
61e7aad847 | |||
742d853b11 | |||
39fe859fe5 | |||
2a0895018a | |||
d1437b3445 | |||
59154d081f | |||
99e8691f0b | |||
b0e82dfd08 | |||
3a64121d72 | |||
99f31cc302 | |||
19de605b79 | |||
9e410254bf | |||
e970fbed80 | |||
9ed62d4414 | |||
9516723c7f | |||
52e6ace847 | |||
c7f50af25c | |||
7d8646b432 | |||
883a30f806 | |||
3bd7655203 | |||
fdfc74521b | |||
3db10ead6a | |||
546eb9f042 | |||
08ef8294df | |||
af5db43248 | |||
697a9efb51 | |||
6297da1c69 | |||
e78ba3942b | |||
28cedae342 | |||
3f9a35e164 | |||
babeeb969c | |||
01e6d45232 | |||
c865ae4df6 | |||
59452aa525 | |||
47c0a451c4 | |||
5b9cce4155 | |||
d6b6327545 | |||
7c2a2f16df | |||
e004838013 | |||
8672c5fb56 | |||
1c788a73c6 | |||
88e9317a41 | |||
27cf1a7348 | |||
326ebc155c | |||
5df82c6b3c | |||
8c6caab299 | |||
daa0053e4b | |||
3990487701 | |||
a6f47c6248 | |||
18935c5b10 | |||
5204cba58d | |||
c9358db883 | |||
2ad78bc56d | |||
ff196d8d92 | |||
7a77b56560 | |||
b982df70ea | |||
a56693547c | |||
699fe59706 | |||
e78bc846b8 | |||
fc2b53d4a5 | |||
82184b3df1 | |||
5cfa484a67 | |||
27a5735dc1 | |||
a714c1045d | |||
a4da3a7afa | |||
190877f5b9 | |||
55f13cbfd1 | |||
b9cbf809b5 | |||
5a0843d98b | |||
fb74ea4455 | |||
42aa102d1d | |||
02aa2981b5 | |||
bde1160562 | |||
dd53c6a842 | |||
3936f652e9 | |||
fa5b77fd2d | |||
cbeb25e0be | |||
3cb961957c | |||
65930716f9 | |||
03e34cc43f | |||
48360ea54c | |||
def6e10b93 | |||
24d27f9f47 | |||
7bdc506ff2 | |||
23b045070a | |||
6a7a38ef02 | |||
0e86fd66df | |||
e375dca9cc | |||
fd9bfa52f3 | |||
5fd5a9bd85 | |||
e75190f767 | |||
96705ed1cc | |||
5c4bfceee7 | |||
85061454a6 | |||
fe4af18196 | |||
183dfadc77 | |||
d3c8c99048 | |||
63774ba5df | |||
53a381222f | |||
7e63d9634a | |||
f6affc14ef | |||
9bf072e122 | |||
3ed4e8f511 | |||
94eb292b16 | |||
a0b23b0faa | |||
04f9852050 | |||
01854ff2d0 | |||
298dba4d6c | |||
bc248a9e30 | |||
7901aad3e7 | |||
6a244b30c1 | |||
17a8d81486 | |||
c114e9b6c3 | |||
672dafc701 | |||
d02ee92508 | |||
42617c8816 | |||
453b5e25be | |||
8fb263dadc | |||
4b4075606e | |||
b02e063302 | |||
cc8c5f7e99 | |||
51ec1af369 | |||
74d7c67d8c | |||
b40c0a868b | |||
b6d13166bc |
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
|
21
.gitignore
vendored
@ -1,17 +1,28 @@
|
|||||||
*.pyc
|
*.pyc
|
||||||
*.pyo
|
*.pyo
|
||||||
*.ui
|
toxygen/toxcore
|
||||||
src/toxcore
|
|
||||||
tests/tests
|
tests/tests
|
||||||
tests/libs
|
tests/libs
|
||||||
tests/.cache
|
tests/.cache
|
||||||
tests/__pycache__
|
tests/__pycache__
|
||||||
src/libs
|
tests/avatars
|
||||||
|
toxygen/libs
|
||||||
.idea
|
.idea
|
||||||
*~
|
*~
|
||||||
*.iml
|
*.iml
|
||||||
*.so
|
*.so
|
||||||
*.log
|
*.log
|
||||||
src/build
|
toxygen/build
|
||||||
src/dist
|
toxygen/dist
|
||||||
*.spec
|
*.spec
|
||||||
|
dist
|
||||||
|
toxygen/avatars
|
||||||
|
toxygen/__pycache__
|
||||||
|
/*.egg-info
|
||||||
|
/*.egg
|
||||||
|
html
|
||||||
|
Toxygen.egg-info
|
||||||
|
*.tox
|
||||||
|
.cache
|
||||||
|
*.db
|
||||||
|
|
||||||
|
53
.travis.yml
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
language: python
|
||||||
|
python:
|
||||||
|
- "3.5"
|
||||||
|
- "3.6"
|
||||||
|
os:
|
||||||
|
- linux
|
||||||
|
dist: trusty
|
||||||
|
notifications:
|
||||||
|
email: false
|
||||||
|
before_install:
|
||||||
|
- sudo apt-get update
|
||||||
|
- sudo apt-get install -y checkinstall build-essential
|
||||||
|
- sudo apt-get install portaudio19-dev
|
||||||
|
- sudo apt-get install libsecret-1-dev
|
||||||
|
- sudo apt-get install libconfig-dev libvpx-dev check -qq
|
||||||
|
install:
|
||||||
|
- pip install sip
|
||||||
|
- pip install pyqt5
|
||||||
|
- pip install pyaudio
|
||||||
|
- pip install opencv-python
|
||||||
|
- pip install pydenticon
|
||||||
|
before_script:
|
||||||
|
# Opus
|
||||||
|
- wget http://downloads.xiph.org/releases/opus/opus-1.0.3.tar.gz
|
||||||
|
- tar xzf opus-1.0.3.tar.gz
|
||||||
|
- cd opus-1.0.3
|
||||||
|
- ./configure
|
||||||
|
- make -j3
|
||||||
|
- sudo make install
|
||||||
|
- cd ..
|
||||||
|
# Libsodium
|
||||||
|
- git clone git://github.com/jedisct1/libsodium.git
|
||||||
|
- cd libsodium
|
||||||
|
- git checkout tags/1.0.3
|
||||||
|
- ./autogen.sh
|
||||||
|
- ./configure && make -j$(nproc)
|
||||||
|
- sudo checkinstall --install --pkgname libsodium --pkgversion 1.0.0 --nodoc -y
|
||||||
|
- sudo ldconfig
|
||||||
|
- cd ..
|
||||||
|
# Toxcore
|
||||||
|
- git clone https://github.com/ingvar1995/toxcore.git --branch=ngc_rebase
|
||||||
|
- cd toxcore
|
||||||
|
- mkdir _build && cd _build
|
||||||
|
- cmake ..
|
||||||
|
- make -j$(nproc)
|
||||||
|
- sudo make install
|
||||||
|
- echo '/usr/local/lib/' | sudo tee -a /etc/ld.so.conf.d/locallib.conf
|
||||||
|
- sudo ldconfig
|
||||||
|
- cd ..
|
||||||
|
- cd ..
|
||||||
|
script:
|
||||||
|
- py.test tests/travis.py
|
||||||
|
- py.test tests/tests.py
|
19
MANIFEST.in
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
include toxygen/images/*.png
|
||||||
|
include toxygen/images/*.ico
|
||||||
|
include toxygen/images/*.gif
|
||||||
|
include toxygen/sounds/*.wav
|
||||||
|
include toxygen/stickers/tox/*.png
|
||||||
|
include toxygen/smileys/default/*.png
|
||||||
|
include toxygen/smileys/default/config.json
|
||||||
|
include toxygen/smileys/animated/*.gif
|
||||||
|
include toxygen/smileys/animated/config.json
|
||||||
|
include toxygen/smileys/starwars/*.gif
|
||||||
|
include toxygen/smileys/starwars/*.png
|
||||||
|
include toxygen/smileys/starwars/config.json
|
||||||
|
include toxygen/smileys/ksk/*.png
|
||||||
|
include toxygen/smileys/ksk/config.json
|
||||||
|
include toxygen/styles/*.qss
|
||||||
|
include toxygen/translations/*.qm
|
||||||
|
include toxygen/libs/libtox.dll
|
||||||
|
include toxygen/libs/libsodium.a
|
||||||
|
include toxygen/bootstrap/nodes.json
|
114
README.md
@ -1,56 +1,80 @@
|
|||||||
# Toxygen
|
# Toxygen
|
||||||
Toxygen is cross-platform [Tox](https://tox.chat/) client written on Python
|
|
||||||
|
|
||||||
[](https://github.com/xveduk/toxygen/releases/latest)
|
Toxygen is powerful cross-platform [Tox](https://tox.chat/) client written in pure Python3.
|
||||||
[](https://github.com/xveduk/toxygen/issues)
|
|
||||||
[](https://raw.githubusercontent.com/xveduk/toxygen/master/LICENSE.md)
|
|
||||||
|
|
||||||
### [Install](/docs/install.md) - [Contribute](/docs/contributing.md) - [Plugins](/docs/plugins.md)
|
### [Install](/docs/install.md) - [Contribute](/docs/contributing.md) - [Plugins](/docs/plugins.md) - [Compile](/docs/compile.md) - [Contact](/docs/contact.md)
|
||||||
|
|
||||||
### Supported OS:
|
### Supported OS: Linux and Windows
|
||||||
- Windows
|
|
||||||
- Linux
|
|
||||||
|
|
||||||
###Features
|
### Features:
|
||||||
- [x] 1v1 messages
|
|
||||||
- [x] File transfers
|
|
||||||
- [x] Audio
|
|
||||||
- [x] Chat history
|
|
||||||
- [x] Screenshots
|
|
||||||
- [x] Name lookups (toxme.io support)
|
|
||||||
- [x] Profile import and export
|
|
||||||
- [x] Inline images
|
|
||||||
- [x] Message splitting
|
|
||||||
- [x] Proxy support
|
|
||||||
- [x] Avatars
|
|
||||||
- [x] Multiprofile
|
|
||||||
- [x] Multilingual
|
|
||||||
- [x] Sound notifications
|
|
||||||
- [x] Contact aliases
|
|
||||||
- [x] Contact blocking
|
|
||||||
- [x] Typing notifications
|
|
||||||
- [x] Changing nospam
|
|
||||||
- [x] File resuming
|
|
||||||
- [x] Save file encryption
|
|
||||||
- [x] Plugins support
|
|
||||||
- [ ] Video
|
|
||||||
- [ ] Group chats
|
|
||||||
- [ ] Read receipts
|
|
||||||
- [ ] Faux offline messaging
|
|
||||||
- [ ] Desktop sharing
|
|
||||||
|
|
||||||
###Downloads
|
- 1v1 messages
|
||||||
[Download last stable version](https://github.com/xveduk/toxygen/archive/master.zip)
|
- File transfers
|
||||||
|
- Audio calls
|
||||||
|
- Video calls
|
||||||
|
- Group chats
|
||||||
|
- Plugins support
|
||||||
|
- Desktop sharing
|
||||||
|
- Chat history
|
||||||
|
- Emoticons
|
||||||
|
- Stickers
|
||||||
|
- Screenshots
|
||||||
|
- Name lookups (toxme.io support)
|
||||||
|
- Save file encryption
|
||||||
|
- Profile import and export
|
||||||
|
- Faux offline messaging
|
||||||
|
- Faux offline file transfers
|
||||||
|
- Inline images
|
||||||
|
- Message splitting
|
||||||
|
- Proxy support
|
||||||
|
- Avatars
|
||||||
|
- Multiprofile
|
||||||
|
- Multilingual
|
||||||
|
- Sound notifications
|
||||||
|
- Contact aliases
|
||||||
|
- Contact blocking
|
||||||
|
- Typing notifications
|
||||||
|
- Changing nospam
|
||||||
|
- File resuming
|
||||||
|
- Read receipts
|
||||||
|
- NGC groups
|
||||||
|
|
||||||
[Download develop version](https://github.com/xveduk/toxygen/archive/develop.zip)
|
### Screenshots
|
||||||
|
|
||||||
[Releases](https://github.com/xveduk/toxygen/releases)
|
|
||||||
|
|
||||||
###Screenshots
|
|
||||||
*Toxygen on Ubuntu and Windows*
|
*Toxygen on Ubuntu and Windows*
|
||||||

|

|
||||||

|

|
||||||
|
|
||||||
###Docs
|
## Forked
|
||||||
[Check /docs/ for more info](/docs/)
|
|
||||||
|
|
||||||
|
This hard-forked from https://github.com/toxygen-project/toxygen
|
||||||
|
```next_gen``` branch.
|
||||||
|
|
||||||
|
https://git.plastiras.org/emdee/toxygen_wrapper needs packaging
|
||||||
|
is making a dependency. Just download it and copy the two directories
|
||||||
|
```wrapper``` and ```wrapper_tests``` into ```toxygen/toxygen```.
|
||||||
|
|
||||||
|
See ToDo.md to the current ToDo list.
|
||||||
|
|
||||||
|
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 9001
|
||||||
|
/relay start ipv4.ssl.weechat
|
||||||
|
```
|
||||||
|
or
|
||||||
|
```
|
||||||
|
/relay add weechat 9000
|
||||||
|
/relay start weechat
|
||||||
|
```
|
||||||
|
and use the File/Connect menu item to connect to weechat. Weechat
|
||||||
|
has a Jabber plugin to enable XMPP:
|
||||||
|
```
|
||||||
|
/python load jabber.el
|
||||||
|
/help jabber
|
||||||
|
```
|
||||||
|
|
||||||
|
Work on Tox on this project is suspended until the
|
||||||
|
[MultiDevice](https://git.plastiras.org/emdee/tox_profile/wiki/MultiDevice-Announcements-POC) problem is solved. Fork me!
|
||||||
|
52
ToDo.md
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
# 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.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## Fix history
|
||||||
|
|
||||||
|
The code is in there but it's not working.
|
||||||
|
|
||||||
|
## Fix Audio
|
||||||
|
|
||||||
|
The code is in there but it's not working. It looks like audio input
|
||||||
|
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.
|
||||||
|
|
||||||
|
## 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.
|
13
build/Dockerfile
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
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
|
33
build/build.sh
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
#!/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
|
@ -1,16 +1,19 @@
|
|||||||
#Compile Toxygen
|
# Compile Toxygen
|
||||||
|
|
||||||
You can compile Toxygen using [PyInstaller](http://www.pyinstaller.org/)
|
You can compile Toxygen using [PyInstaller](http://www.pyinstaller.org/)
|
||||||
|
|
||||||
Install PyInstaller:
|
Use Dockerfile and build script from `build` directory:
|
||||||
``pip install pyinstaller``
|
|
||||||
|
|
||||||
On Linux:
|
1. Build image:
|
||||||
|
```
|
||||||
|
docker build -t toxygen .
|
||||||
|
```
|
||||||
|
|
||||||
``pyinstaller --windowed main.py``
|
2. Run container:
|
||||||
|
```
|
||||||
|
docker run -it toxygen bash
|
||||||
|
```
|
||||||
|
|
||||||
On Windows:
|
3. Execute `build.sh` script:
|
||||||
|
|
||||||
``pyinstaller --windowed --icon images/icon.ico main.py``
|
```./build.sh```
|
||||||
|
|
||||||
Don't forget to copy /images/, /sounds/, /translations/, /styles/, to /dist/main/
|
|
||||||
|
6
docs/contact.md
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
# Contact us:
|
||||||
|
|
||||||
|
1) https://git.plastiras.org/emdee/toxygen/issues
|
||||||
|
|
||||||
|
2) Use Toxygen Tox Group (NGC) -
|
||||||
|
ID: 59D68B2709E81A679CF91416CB0E3692851C6CFCABEFF98B7131E3805A6D75FA
|
@ -1,22 +1,25 @@
|
|||||||
|
# Issues
|
||||||
|
|
||||||
#Issues
|
|
||||||
|
|
||||||
Help us find all bugs in Toxygen! Please provide following info:
|
Help us find all bugs in Toxygen! Please provide following info:
|
||||||
|
|
||||||
- OS
|
- OS
|
||||||
- Toxygen version
|
- Toxygen version
|
||||||
- Toxygen executable info - .py or precompiled binary
|
- Toxygen executable info - python executable (.py), precompiled binary, from package etc.
|
||||||
- Steps to reproduce the bug
|
- Steps to reproduce the bug
|
||||||
|
|
||||||
Want to see new feature in Toxygen? [Open issue!](https://github.com/xveduk/toxygen/issues)
|
Want to see new feature in Toxygen?
|
||||||
|
[Ask for it!](https://git.plastiras.org/emdee/toxygen/issues)
|
||||||
|
|
||||||
#Pull requests
|
# Pull requests
|
||||||
|
|
||||||
Developer? Feel free to open pull request. Our dev team is small so we glad to get help.
|
Developer? Feel free to open pull request. Our dev team is small so we glad to get help.
|
||||||
Don't know what to do? Impove UI, fix [issues](https://github.com/xveduk/toxygen/issues) or implement features from our TODO list.
|
Don't know what to do? Improve UI, fix
|
||||||
You can find our TODO's in code and [here](/README.md)
|
[issues](https://git.plastiras.org/emdee/toxygen/issues)
|
||||||
|
or implement features from our TODO list.
|
||||||
|
You can find our TODO's in code, issues list and [here](/README.md). Also you can implement [plugins](/docs/plugins.md) for Toxygen.
|
||||||
|
|
||||||
#Translations
|
Note that we have a lot of branches for different purposes. Master branch is for stable versions (releases) only, so I recommend to open PR's to develop branch. Development of next Toxygen version usually goes there. Other branches used for implementing different tasks such as file transfers improvements or audio calls implementation etc.
|
||||||
|
|
||||||
Help us translate Toxygen! Translate can be created using pyside-lupdate and QT Linguist.
|
# Translations
|
||||||
|
|
||||||
|
Help us translate Toxygen! Translation can be created using pylupdate (``pylupdate5 toxygen.pro``) and QT Linguist.
|
||||||
|
@ -1,44 +1,44 @@
|
|||||||
# How to install Toxygen
|
# How to install Toxygen
|
||||||
|
|
||||||
|
### Linux
|
||||||
|
|
||||||
|
1. Install [c-toxcore](https://github.com/TokTok/c-toxcore/)
|
||||||
|
2. Install PortAudio:
|
||||||
|
``sudo apt-get install portaudio19-dev``
|
||||||
|
3. For 32-bit Linux install PyQt5: ``sudo apt-get install python3-pyqt5``
|
||||||
|
4. Install [OpenCV](http://docs.opencv.org/trunk/d7/d9f/tutorial_linux_install.html) or via ``sudo pip3 install opencv-python``
|
||||||
|
5. Install [toxygen](https://git.plastiras.org/emdee/toxygen/)
|
||||||
|
6. Run toxygen using ``toxygen`` command.
|
||||||
|
|
||||||
## From source code (recommended for developers)
|
## From source code (recommended for developers)
|
||||||
|
|
||||||
### Windows
|
### Windows
|
||||||
|
|
||||||
1. [Download and install latest Python 2.7](https://www.python.org/downloads/windows/)
|
Note: 32-bit Python isn't supported due to bug with videocalls. It is strictly recommended to use 64-bit Python.
|
||||||
2. [Install PySide](https://pypi.python.org/pypi/PySide/1.2.4) *(PyQt4 support will be added later)*
|
|
||||||
3. Install PyAudio: ``python -m pip install pyaudio``
|
|
||||||
4. [Download toxygen](https://github.com/xveduk/toxygen/archive/master.zip)
|
|
||||||
5. Unpack archive
|
|
||||||
6. Download latest libtox.dll build, download latest libsodium.a build, put it into \src\libs\
|
|
||||||
7. Run \src\main.py
|
|
||||||
|
|
||||||
[libtox.dll for 32-bit Python](https://build.tox.chat/view/libtoxcore/job/libtoxcore_build_windows_x86_shared_release/lastSuccessfulBuild/artifact/libtoxcore_build_windows_x86_shared_release.zip)
|
|
||||||
|
|
||||||
[libtox.dll for 64-bit Python](https://build.tox.chat/view/libtoxcore/job/libtoxcore_build_windows_x86-64_shared_release/lastSuccessfulBuild/artifact/libtoxcore_build_windows_x86-64_shared_release.zip)
|
|
||||||
|
|
||||||
[libsodium.a for 32-bit Python](https://build.tox.chat/view/libsodium/job/libsodium_build_windows_x86_static_release/lastSuccessfulBuild/artifact/libsodium_build_windows_x86_static_release.zip)
|
|
||||||
|
|
||||||
[libsodium.a for 64-bit Python](https://build.tox.chat/view/libsodium/job/libsodium_build_windows_x86-64_static_release/lastSuccessfulBuild/artifact/libsodium_build_windows_x86-64_static_release.zip)
|
|
||||||
|
|
||||||
|
1. [Download and install latest Python 3 64-bit](https://www.python.org/downloads/windows/)
|
||||||
|
2. Install PyQt5: ``pip install pyqt5``
|
||||||
|
3. Install PyAudio: ``pip install pyaudio``
|
||||||
|
4. Install numpy: ``pip install numpy``
|
||||||
|
5. Install OpenCV: ``pip install opencv-python``
|
||||||
|
6. [Download toxygen](https://github.com/toxygen-project/toxygen/archive/master.zip)
|
||||||
|
7. Unpack archive
|
||||||
|
8. Download latest libtox.dll build, download latest libsodium.a build, put it into \toxygen\libs\
|
||||||
|
9. Run \toxygen\main.py.
|
||||||
|
|
||||||
### Linux
|
### Linux
|
||||||
|
|
||||||
- Install Python2.7:
|
1. Install latest Python3:
|
||||||
``sudo apt-get install python2.7``
|
``sudo apt-get install python3``
|
||||||
- [Install PySide](https://wiki.qt.io/PySide_Binaries_Linux) *(PyQt4 support will be added later)*
|
2. Install PyQt5: ``sudo apt-get install python3-pyqt5`` or ``sudo pip3 install pyqt5``
|
||||||
- Install PyAudio:
|
3. Install [toxcore](https://github.com/TokTok/c-toxcore) with toxav support)
|
||||||
```bash
|
4. Install PyAudio:
|
||||||
sudo apt-get install portaudio19-dev
|
``sudo apt-get install portaudio19-dev`` and ``sudo apt-get install python3-pyaudio`` (or ``sudo pip3 install pyaudio``)
|
||||||
sudo pip install pyaudio
|
5. Install NumPy: ``sudo pip3 install numpy``
|
||||||
```
|
6. Install [OpenCV](http://docs.opencv.org/trunk/d7/d9f/tutorial_linux_install.html) or via ``sudo pip3 install opencv-python``
|
||||||
- [Download toxygen](https://github.com/xveduk/toxygen/archive/master.zip)
|
7. [Download toxygen](https://git.plastiras.org/emdee/toxygen/)
|
||||||
- Unpack archive
|
8. Unpack archive
|
||||||
- Install [toxcore](https://github.com/irungentoo/toxcore/blob/master/INSTALL.md) in your system (install in /usr/lib/)
|
9. Run app:
|
||||||
- Run app:
|
``python3 main.py``
|
||||||
``python main.py``
|
|
||||||
|
|
||||||
## Use precompiled binary:
|
Optional: install toxygen using setup.py: ``python3 setup.py install``
|
||||||
[Check our releases page](https://github.com/xveduk/toxygen/releases)
|
|
||||||
|
|
||||||
## Compile Toxygen
|
|
||||||
Check [compile.md](/docs/compile.md) for more info
|
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
#Plugins API
|
# Plugins API
|
||||||
|
|
||||||
In Toxygen plugin is single python module (.py file) and directory with data associated with it.
|
In Toxygen plugin is single python module (.py file) and directory with data associated with it.
|
||||||
Every module must contain one class derived from PluginSuperClass defined in [plugin_super_class.py](/src/plugins/plugin_super_class.py). Instance of this class will be created by PluginLoader class (defined in [plugin_support.py](/src/plugin_support.py) ). This class can enable/disable plugins and send data to it.
|
Every module must contain one class derived from PluginSuperClass defined in [plugin_super_class.py](/src/plugins/plugin_super_class.py). Instance of this class will be created by PluginLoader class (defined in [plugin_support.py](/src/plugin_support.py) ). This class can enable/disable plugins and send data to it.
|
||||||
|
|
||||||
Every plugin has it's own full name and unique short name (1-5 symbols). Main app can get it using special methods.
|
Every plugin has its own full name and unique short name (1-5 symbols). Main app can get it using special methods.
|
||||||
|
|
||||||
All plugin's data should be stored in following structure:
|
All plugin's data should be stored in following structure:
|
||||||
|
|
||||||
@ -12,42 +12,46 @@ All plugin's data should be stored in following structure:
|
|||||||
|---plugin_short_name.py
|
|---plugin_short_name.py
|
||||||
|---/plugin_short_name/
|
|---/plugin_short_name/
|
||||||
|---settings.json
|
|---settings.json
|
||||||
|
|---readme.txt
|
||||||
|---logs.txt
|
|---logs.txt
|
||||||
|---other_files
|
|---other_files
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Plugin MUST override:
|
||||||
|
- __init__ with params: tox (Tox instance), profile (Profile instance), settings (Settings instance), encrypt_save (ToxES instance). Call super().__init__ with params plugin_full_name, plugin_short_name, tox, profile, settings, encrypt_save.
|
||||||
|
|
||||||
Plugin can override following methods:
|
Plugin can override following methods:
|
||||||
- get_description - this method should return plugin description.
|
- get_description - this method should return plugin description.
|
||||||
- get_menu - plugins allowed to add items in friend menu. You can open this menu making right click on friend in friends list. This method should return list of QAction's. Plugin must connect to QAction's triggered() signal.
|
- get_menu - plugins allowed to add items in friend menu. User can open this menu making right click on friend in friends list. This method should return list of QAction's. Plugin must connect to QAction's triggered() signal.
|
||||||
- get_window - plugins can have GUI, this method should return window instance or None for plugins without GUI.
|
- get_window - plugins can have GUI, this method should return window instance or None for plugins without GUI.
|
||||||
- start - plugin was started.
|
- start - plugin was started.
|
||||||
- stop - plugin was stopped.
|
- stop - plugin was stopped.
|
||||||
- close - app is closing, stop plugin.
|
- close - app is closing, stop plugin.
|
||||||
- command - new command to plugin. Command can be entered in message field in format '/plugin <plugin_short_name> <command>'. Command 'help' should show user list of supported commands.
|
- command - new command to plugin. Command can be entered in message field in format '/plugin <plugin_short_name> <command>'. Command 'help' should show list of supported commands.
|
||||||
- lossless_packet - callback - incoming lossless packet from friend.
|
- lossless_packet - callback - incoming lossless packet from friend.
|
||||||
- lossy_packet - callback - incoming lossy packet from friend.
|
- lossy_packet - callback - incoming lossy packet from friend.
|
||||||
- friend_connected - callback - friend became online.
|
- friend_connected - callback - friend became online. Note that it called from friend_connection_status callback so friend is not really connected and ready for sending packets.
|
||||||
|
|
||||||
Other methods:
|
Other methods:
|
||||||
- send_lossless - this method send custom lossless packet. Plugins MUST send lossless packets using this method.
|
- send_lossless - this method sends custom lossless packet. Plugins MUST send lossless packets using this method.
|
||||||
- send_lossy - this method send custom lossy packet. Plugins MUST send lossy packets using this method.
|
- send_lossy - this method sends custom lossy packet. Plugins MUST send lossy packets using this method.
|
||||||
- load_settings - loads settings stored in default location.
|
- load_settings - loads settings stored in default location.
|
||||||
- save_settings - saves settings to default location.
|
- save_settings - saves settings to default location.
|
||||||
- load_translator - loads translations. Translations must be stored in directory with plugin's data. Files with translations must have the same name as in main app.
|
- load_translator - loads translations. Translations must be stored in directory with plugin's data. Files with translations must have the same name as in main app (example: ru_RU.qm).
|
||||||
|
|
||||||
About import:
|
About import:
|
||||||
|
|
||||||
import statement will not work in case you import module that wasn't previously imported by main program and user use precompiled binary. It's recommended to use imp module and dynamic import instead.
|
Import statement will not work in case you import module that wasn't previously imported by main program and user uses precompiled binary. It's recommended to use importlib module instead: importlib.import_module(module_name)
|
||||||
|
|
||||||
About GUI:
|
About GUI:
|
||||||
|
|
||||||
It's strictly recommended to support both PySide and PyQt4 in GUI.
|
GUI is available via PyQt5. Plugin can have no GUI at all.
|
||||||
|
|
||||||
Exceptions:
|
Exceptions:
|
||||||
|
|
||||||
Plugin's methods should not raise exceptions.
|
Plugin's methods MUST NOT raise exceptions.
|
||||||
|
|
||||||
#Examples
|
# Examples
|
||||||
|
|
||||||
You can find example of a plugin in [official repo](https://github.com/ingvar1995/toxygen_plugins)
|
You can find examples in [official repo](https://github.com/toxygen-project/toxygen_plugins)
|
||||||
|
|
||||||
|
@ -1,20 +1,22 @@
|
|||||||
#Plugins
|
# Plugins
|
||||||
|
|
||||||
Toxygen is the first [Tox](https://tox.chat/) client with plugins support. Plugin is Python module and directory with plugin's data which provide some additional functionality.
|
Toxygen is the first [Tox](https://tox.chat/) client with plugins support. Plugin is Python 3.5 - 3.6 module (.py file) and directory with plugin's data which provide some additional functionality.
|
||||||
|
|
||||||
#How to write plugin
|
# How to write plugin
|
||||||
|
|
||||||
Check [Plugin API](/docs/plugin_api.md) for more info
|
Check [Plugin API](/docs/plugin_api.md) for more info
|
||||||
|
|
||||||
#How to install plugin
|
# How to install plugin
|
||||||
|
|
||||||
Toxygen comes without preinstalled plugins.
|
Toxygen comes without preinstalled plugins.
|
||||||
|
|
||||||
1. Put plugin and directory with it's data into /src/plugins/
|
1. Put plugin and directory with its data into /src/plugins/ or import it via GUI (In menu: Plugins => Import plugin)
|
||||||
2. Restart Toxygen
|
2. Restart Toxygen or choose Plugins => Reload plugins in menu.
|
||||||
|
|
||||||
#Plugins list
|
## Note: /src/plugins/ should contain plugin_super_class.py and __init__.py
|
||||||
|
|
||||||
|
# Plugins list
|
||||||
|
|
||||||
WARNING: It is unsecure to install plugin not from this list!
|
WARNING: It is unsecure to install plugin not from this list!
|
||||||
|
|
||||||
[Main repo](https://github.com/ingvar1995/toxygen_plugins)
|
[Main repo](https://github.com/toxygen-project/toxygen_plugins)
|
13
docs/smileys_and_stickers.md
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
# Smileys
|
||||||
|
|
||||||
|
Toxygen support smileys. Smiley is small picture which replaces some symbol or combination of symbols. If you want to create your own smiley pack, create directory in src/smileys/. This directory must contain images with smileys and config.json. Example of config.json:
|
||||||
|
|
||||||
|
{":)": "one.png", ":(": "two.png"}
|
||||||
|
|
||||||
|
Animated smileys (.gif) are supported too.
|
||||||
|
|
||||||
|
# Stickers
|
||||||
|
|
||||||
|
Sticker is inline image. If you want to create your own sticker pack, create directory in src/stickers/ and place your stickers there.
|
||||||
|
|
||||||
|
Users can import smileys and stickers using menu: Settings -> Interface
|
BIN
docs/ubuntu.png
Executable file → Normal file
Before Width: | Height: | Size: 94 KiB After Width: | Height: | Size: 107 KiB |
BIN
docs/windows.png
Executable file → Normal file
Before Width: | Height: | Size: 317 KiB After Width: | Height: | Size: 71 KiB |
6
requirements.txt
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
PyQt5
|
||||||
|
PyAudio
|
||||||
|
numpy
|
||||||
|
opencv-python
|
||||||
|
pydenticon
|
||||||
|
cv2
|
93
setup.py
Normal file
@ -0,0 +1,93 @@
|
|||||||
|
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
|
||||||
|
)
|
109
src/avwidgets.py
@ -1,109 +0,0 @@
|
|||||||
try:
|
|
||||||
from PySide import QtCore, QtGui
|
|
||||||
except ImportError:
|
|
||||||
from PyQt4 import QtCore, QtGui
|
|
||||||
import widgets
|
|
||||||
import profile
|
|
||||||
import util
|
|
||||||
import pyaudio
|
|
||||||
import wave
|
|
||||||
import settings
|
|
||||||
from util import curr_directory
|
|
||||||
|
|
||||||
|
|
||||||
class IncomingCallWidget(widgets.CenteredWidget):
|
|
||||||
|
|
||||||
def __init__(self, friend_number, text, name):
|
|
||||||
super(IncomingCallWidget, self).__init__()
|
|
||||||
self.setWindowFlags(QtCore.Qt.CustomizeWindowHint | QtCore.Qt.WindowTitleHint | QtCore.Qt.WindowStaysOnTopHint)
|
|
||||||
self.resize(QtCore.QSize(500, 270))
|
|
||||||
self.avatar_label = QtGui.QLabel(self)
|
|
||||||
self.avatar_label.setGeometry(QtCore.QRect(10, 20, 64, 64))
|
|
||||||
self.avatar_label.setScaledContents(False)
|
|
||||||
self.name = widgets.DataLabel(self)
|
|
||||||
self.name.setGeometry(QtCore.QRect(90, 20, 300, 25))
|
|
||||||
font = QtGui.QFont()
|
|
||||||
font.setFamily("Times New Roman")
|
|
||||||
font.setPointSize(16)
|
|
||||||
font.setBold(True)
|
|
||||||
self.name.setFont(font)
|
|
||||||
self.call_type = widgets.DataLabel(self)
|
|
||||||
self.call_type.setGeometry(QtCore.QRect(90, 55, 300, 25))
|
|
||||||
self.call_type.setFont(font)
|
|
||||||
self.accept_audio = QtGui.QPushButton(self)
|
|
||||||
self.accept_audio.setGeometry(QtCore.QRect(20, 100, 150, 150))
|
|
||||||
self.accept_video = QtGui.QPushButton(self)
|
|
||||||
self.accept_video.setGeometry(QtCore.QRect(170, 100, 150, 150))
|
|
||||||
self.decline = QtGui.QPushButton(self)
|
|
||||||
self.decline.setGeometry(QtCore.QRect(320, 100, 150, 150))
|
|
||||||
pixmap = QtGui.QPixmap(util.curr_directory() + '/images/accept_audio.png')
|
|
||||||
icon = QtGui.QIcon(pixmap)
|
|
||||||
self.accept_audio.setIcon(icon)
|
|
||||||
pixmap = QtGui.QPixmap(util.curr_directory() + '/images/accept_video.png')
|
|
||||||
icon = QtGui.QIcon(pixmap)
|
|
||||||
self.accept_video.setIcon(icon)
|
|
||||||
pixmap = QtGui.QPixmap(util.curr_directory() + '/images/decline_call.png')
|
|
||||||
icon = QtGui.QIcon(pixmap)
|
|
||||||
self.decline.setIcon(icon)
|
|
||||||
self.accept_audio.setIconSize(QtCore.QSize(150, 150))
|
|
||||||
self.accept_video.setIconSize(QtCore.QSize(140, 140))
|
|
||||||
self.decline.setIconSize(QtCore.QSize(140, 140))
|
|
||||||
self.accept_audio.setStyleSheet("QPushButton { border: none }")
|
|
||||||
self.accept_video.setStyleSheet("QPushButton { border: none }")
|
|
||||||
self.decline.setStyleSheet("QPushButton { border: none }")
|
|
||||||
self.setWindowTitle(text)
|
|
||||||
self.name.setText(name)
|
|
||||||
self.call_type.setText(text)
|
|
||||||
pr = profile.Profile.get_instance()
|
|
||||||
self.accept_audio.clicked.connect(lambda: pr.accept_call(friend_number, True, False) or self.stop())
|
|
||||||
# self.accept_video.clicked.connect(lambda: pr.start_call(friend_number, True, True))
|
|
||||||
self.decline.clicked.connect(lambda: pr.stop_call(friend_number, False) or self.stop())
|
|
||||||
|
|
||||||
class SoundPlay(QtCore.QThread):
|
|
||||||
|
|
||||||
def __init__(self):
|
|
||||||
QtCore.QThread.__init__(self)
|
|
||||||
|
|
||||||
def run(self):
|
|
||||||
class AudioFile(object):
|
|
||||||
chunk = 1024
|
|
||||||
|
|
||||||
def __init__(self, fl):
|
|
||||||
self.stop = False
|
|
||||||
self.wf = wave.open(fl, 'rb')
|
|
||||||
self.p = pyaudio.PyAudio()
|
|
||||||
self.stream = self.p.open(
|
|
||||||
format=self.p.get_format_from_width(self.wf.getsampwidth()),
|
|
||||||
channels=self.wf.getnchannels(),
|
|
||||||
rate=self.wf.getframerate(),
|
|
||||||
output=True
|
|
||||||
)
|
|
||||||
|
|
||||||
def play(self):
|
|
||||||
data = self.wf.readframes(self.chunk)
|
|
||||||
while data and not self.stop:
|
|
||||||
self.stream.write(data)
|
|
||||||
data = self.wf.readframes(self.chunk)
|
|
||||||
|
|
||||||
def close(self):
|
|
||||||
self.stream.close()
|
|
||||||
self.p.terminate()
|
|
||||||
|
|
||||||
self.a = AudioFile(curr_directory() + '/sounds/call.wav')
|
|
||||||
self.a.play()
|
|
||||||
self.a.close()
|
|
||||||
|
|
||||||
if settings.Settings.get_instance()['calls_sound']:
|
|
||||||
self.thread = SoundPlay()
|
|
||||||
self.thread.start()
|
|
||||||
else:
|
|
||||||
self.thread = None
|
|
||||||
|
|
||||||
def stop(self):
|
|
||||||
if self.thread is not None:
|
|
||||||
self.thread.a.stop = True
|
|
||||||
self.thread.wait()
|
|
||||||
self.close()
|
|
||||||
|
|
||||||
def set_pixmap(self, pixmap):
|
|
||||||
self.avatar_label.setPixmap(pixmap)
|
|
@ -1,87 +0,0 @@
|
|||||||
import random
|
|
||||||
|
|
||||||
|
|
||||||
class Node(object):
|
|
||||||
def __init__(self, ip, port, tox_key, rand):
|
|
||||||
self._ip, self._port, self._tox_key, self.rand = ip, port, tox_key, rand
|
|
||||||
|
|
||||||
def get_data(self):
|
|
||||||
return self._ip, self._port, self._tox_key
|
|
||||||
|
|
||||||
|
|
||||||
def node_generator():
|
|
||||||
nodes = []
|
|
||||||
ips = [
|
|
||||||
"144.76.60.215", "23.226.230.47", "195.154.119.113", "biribiri.org",
|
|
||||||
"46.38.239.179", "178.62.250.138", "130.133.110.14", "104.167.101.29",
|
|
||||||
"205.185.116.116", "198.98.51.198", "80.232.246.79", "108.61.165.198",
|
|
||||||
"212.71.252.109", "194.249.212.109", "185.25.116.107", "192.99.168.140",
|
|
||||||
"46.101.197.175", "95.215.46.114", "5.189.176.217", "148.251.23.146",
|
|
||||||
"104.223.122.15", "78.47.114.252", "d4rk4.ru", "81.4.110.149",
|
|
||||||
"95.31.20.151", "104.233.104.126", "51.254.84.212", "home.vikingmakt.com.br",
|
|
||||||
"5.135.59.163", "185.58.206.164", "188.244.38.183", "mrflibble.c4.ee",
|
|
||||||
"82.211.31.116", "128.199.199.197", "103.230.156.174", "91.121.66.124",
|
|
||||||
"92.54.84.70", "tox1.privacydragon.me"
|
|
||||||
]
|
|
||||||
ports = [
|
|
||||||
33445, 33445, 33445, 33445,
|
|
||||||
33445, 33445, 33445, 33445,
|
|
||||||
33445, 33445, 33445, 33445,
|
|
||||||
33445, 33445, 33445, 33445,
|
|
||||||
443, 33445, 5190, 2306,
|
|
||||||
33445, 33445, 1813, 33445,
|
|
||||||
33445, 33445, 33445, 33445,
|
|
||||||
33445, 33445, 33445, 33445,
|
|
||||||
33445, 33445, 33445, 33445,
|
|
||||||
33445, 33445
|
|
||||||
]
|
|
||||||
ids = [
|
|
||||||
"04119E835DF3E78BACF0F84235B300546AF8B936F035185E2A8E9E0A67C8924F",
|
|
||||||
"A09162D68618E742FFBCA1C2C70385E6679604B2D80EA6E84AD0996A1AC8A074",
|
|
||||||
"E398A69646B8CEACA9F0B84F553726C1C49270558C57DF5F3C368F05A7D71354",
|
|
||||||
"F404ABAA1C99A9D37D61AB54898F56793E1DEF8BD46B1038B9D822E8460FAB67",
|
|
||||||
"F5A1A38EFB6BD3C2C8AF8B10D85F0F89E931704D349F1D0720C3C4059AF2440A",
|
|
||||||
"788236D34978D1D5BD822F0A5BEBD2C53C64CC31CD3149350EE27D4D9A2F9B6B",
|
|
||||||
"461FA3776EF0FA655F1A05477DF1B3B614F7D6B124F7DB1DD4FE3C08B03B640F",
|
|
||||||
"5918AC3C06955962A75AD7DF4F80A5D7C34F7DB9E1498D2E0495DE35B3FE8A57",
|
|
||||||
"A179B09749AC826FF01F37A9613F6B57118AE014D4196A0E1105A98F93A54702",
|
|
||||||
"1D5A5F2F5D6233058BF0259B09622FB40B482E4FA0931EB8FD3AB8E7BF7DAF6F",
|
|
||||||
"CF6CECA0A14A31717CC8501DA51BE27742B70746956E6676FF423A529F91ED5D",
|
|
||||||
"8E7D0B859922EF569298B4D261A8CCB5FEA14FB91ED412A7603A585A25698832",
|
|
||||||
"C4CEB8C7AC607C6B374E2E782B3C00EA3A63B80D4910B8649CCACDD19F260819",
|
|
||||||
"3CEE1F054081E7A011234883BC4FC39F661A55B73637A5AC293DDF1251D9432B",
|
|
||||||
"DA4E4ED4B697F2E9B000EEFE3A34B554ACD3F45F5C96EAEA2516DD7FF9AF7B43",
|
|
||||||
"6A4D0607A296838434A6A7DDF99F50EF9D60A2C510BBF31FE538A25CB6B4652F",
|
|
||||||
"CD133B521159541FB1D326DE9850F5E56A6C724B5B8E5EB5CD8D950408E95707",
|
|
||||||
"5823FB947FF24CF83DDFAC3F3BAA18F96EA2018B16CC08429CB97FA502F40C23",
|
|
||||||
"2B2137E094F743AC8BD44652C55F41DFACC502F125E99E4FE24D40537489E32F",
|
|
||||||
"7AED21F94D82B05774F697B209628CD5A9AD17E0C073D9329076A4C28ED28147",
|
|
||||||
"0FB96EEBFB1650DDB52E70CF773DDFCABE25A95CC3BB50FC251082E4B63EF82A",
|
|
||||||
"1C5293AEF2114717547B39DA8EA6F1E331E5E358B35F9B6B5F19317911C5F976",
|
|
||||||
"53737F6D47FA6BD2808F378E339AF45BF86F39B64E79D6D491C53A1D522E7039",
|
|
||||||
"9E7BD4793FFECA7F32238FA2361040C09025ED3333744483CA6F3039BFF0211E",
|
|
||||||
"9CA69BB74DE7C056D1CC6B16AB8A0A38725C0349D187D8996766958584D39340",
|
|
||||||
"EDEE8F2E839A57820DE3DA4156D88350E53D4161447068A3457EE8F59F362414",
|
|
||||||
"AEC204B9A4501412D5F0BB67D9C81B5DB3EE6ADA64122D32A3E9B093D544327D",
|
|
||||||
"188E072676404ED833A4E947DC1D223DF8EFEBE5F5258573A236573688FB9761",
|
|
||||||
"2D320F971EF2CA18004416C2AAE7BA52BF7949DB34EA8E2E21AF67BD367BE211",
|
|
||||||
"24156472041E5F220D1FA11D9DF32F7AD697D59845701CDD7BE7D1785EB9DB39",
|
|
||||||
"15A0F9684E2423F9F46CFA5A50B562AE42525580D840CC50E518192BF333EE38",
|
|
||||||
"FAAB17014F42F7F20949F61E55F66A73C230876812A9737F5F6D2DCE4D9E4207",
|
|
||||||
"AF97B76392A6474AF2FD269220FDCF4127D86A42EF3A242DF53A40A268A2CD7C",
|
|
||||||
"B05C8869DBB4EDDD308F43C1A974A20A725A36EACCA123862FDE9945BF9D3E09",
|
|
||||||
"5C4C7A60183D668E5BD8B3780D1288203E2F1BAE4EEF03278019E21F86174C1D",
|
|
||||||
"4E3F7D37295664BBD0741B6DBCB6431D6CD77FC4105338C2FC31567BF5C8224A",
|
|
||||||
"5625A62618CB4FCA70E147A71B29695F38CC65FF0CBD68AD46254585BE564802",
|
|
||||||
"31910C0497D347FF160D6F3A6C0E317BAFA71E8E03BC4CBB2A185C9D4FB8B31E"
|
|
||||||
]
|
|
||||||
for i in xrange(len(ips)):
|
|
||||||
nodes.append(Node(ips[i], ports[i], ids[i], random.randint(0, 1000000)))
|
|
||||||
arr = sorted(nodes, key=lambda x: x.rand)[:4]
|
|
||||||
for elem in arr:
|
|
||||||
yield elem.get_data()
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
for elem in node_generator():
|
|
||||||
print str(elem)
|
|
314
src/callbacks.py
@ -1,314 +0,0 @@
|
|||||||
try:
|
|
||||||
from PySide import QtCore
|
|
||||||
except ImportError:
|
|
||||||
from PyQt4 import QtCore
|
|
||||||
from notifications import *
|
|
||||||
from settings import Settings
|
|
||||||
from profile import Profile
|
|
||||||
from toxcore_enums_and_consts import *
|
|
||||||
from toxav_enums import *
|
|
||||||
from tox import bin_to_string
|
|
||||||
from plugin_support import PluginLoader
|
|
||||||
|
|
||||||
|
|
||||||
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))
|
|
||||||
|
|
||||||
# -----------------------------------------------------------------------------------------------------------------
|
|
||||||
# Callbacks - current user
|
|
||||||
# -----------------------------------------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
|
|
||||||
def self_connection_status(tox_link):
|
|
||||||
"""
|
|
||||||
Current user changed connection status (offline, UDP, TCP)
|
|
||||||
"""
|
|
||||||
def wrapped(tox, connection, user_data):
|
|
||||||
print 'Connection status: ', str(connection)
|
|
||||||
profile = Profile.get_instance()
|
|
||||||
if profile.status is None:
|
|
||||||
status = tox_link.self_get_status()
|
|
||||||
invoke_in_main_thread(profile.set_status, status)
|
|
||||||
elif connection == TOX_CONNECTION['NONE']:
|
|
||||||
invoke_in_main_thread(profile.set_status, None)
|
|
||||||
return wrapped
|
|
||||||
|
|
||||||
|
|
||||||
# -----------------------------------------------------------------------------------------------------------------
|
|
||||||
# Callbacks - friends
|
|
||||||
# -----------------------------------------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
|
|
||||||
def friend_status(tox, friend_num, new_status, user_data):
|
|
||||||
"""
|
|
||||||
Check friend's status (none, busy, away)
|
|
||||||
"""
|
|
||||||
print "Friend's #{} status changed! New status: {}".format(friend_num, new_status)
|
|
||||||
profile = Profile.get_instance()
|
|
||||||
friend = profile.get_friend_by_number(friend_num)
|
|
||||||
if friend.status is None and Settings.get_instance()['sound_notifications'] and profile.status != TOX_USER_STATUS['BUSY']:
|
|
||||||
sound_notification(SOUND_NOTIFICATION['FRIEND_CONNECTION_STATUS'])
|
|
||||||
invoke_in_main_thread(friend.set_status, new_status)
|
|
||||||
invoke_in_main_thread(profile.update_filtration)
|
|
||||||
|
|
||||||
|
|
||||||
def friend_connection_status(tox, friend_num, new_status, user_data):
|
|
||||||
"""
|
|
||||||
Check friend's connection status (offline, udp, tcp)
|
|
||||||
"""
|
|
||||||
print "Friend #{} connected! Friend's status: {}".format(friend_num, new_status)
|
|
||||||
profile = Profile.get_instance()
|
|
||||||
friend = profile.get_friend_by_number(friend_num)
|
|
||||||
if new_status == TOX_CONNECTION['NONE']:
|
|
||||||
invoke_in_main_thread(profile.friend_exit, friend_num)
|
|
||||||
invoke_in_main_thread(profile.update_filtration)
|
|
||||||
if Settings.get_instance()['sound_notifications'] and profile.status != TOX_USER_STATUS['BUSY']:
|
|
||||||
sound_notification(SOUND_NOTIFICATION['FRIEND_CONNECTION_STATUS'])
|
|
||||||
elif friend.status is None:
|
|
||||||
invoke_in_main_thread(profile.send_avatar, friend_num)
|
|
||||||
PluginLoader.get_instance().friend_online(friend_num)
|
|
||||||
|
|
||||||
|
|
||||||
def friend_name(tox, friend_num, name, size, user_data):
|
|
||||||
"""
|
|
||||||
Friend changed his name
|
|
||||||
"""
|
|
||||||
profile = Profile.get_instance()
|
|
||||||
friend = profile.get_friend_by_number(friend_num)
|
|
||||||
print 'New name: ', str(friend_num), str(name)
|
|
||||||
invoke_in_main_thread(friend.set_name, name)
|
|
||||||
if profile.get_active_number() == friend_num:
|
|
||||||
invoke_in_main_thread(profile.set_active)
|
|
||||||
|
|
||||||
|
|
||||||
def friend_status_message(tox, friend_num, status_message, size, user_data):
|
|
||||||
"""
|
|
||||||
:return: function for callback friend_status_message. It updates friend's status message
|
|
||||||
and calls window repaint
|
|
||||||
"""
|
|
||||||
profile = Profile.get_instance()
|
|
||||||
friend = profile.get_friend_by_number(friend_num)
|
|
||||||
invoke_in_main_thread(friend.set_status_message, status_message)
|
|
||||||
print 'User #{} has new status: {}'.format(friend_num, status_message)
|
|
||||||
if profile.get_active_number() == friend_num:
|
|
||||||
invoke_in_main_thread(profile.set_active)
|
|
||||||
|
|
||||||
|
|
||||||
def friend_message(window, tray):
|
|
||||||
"""
|
|
||||||
New message from friend
|
|
||||||
"""
|
|
||||||
def wrapped(tox, friend_number, message_type, message, size, user_data):
|
|
||||||
profile = Profile.get_instance()
|
|
||||||
settings = Settings.get_instance()
|
|
||||||
invoke_in_main_thread(profile.new_message, friend_number, message_type, message)
|
|
||||||
if not window.isActiveWindow():
|
|
||||||
friend = profile.get_friend_by_number(friend_number)
|
|
||||||
if settings['notifications'] and profile.status != TOX_USER_STATUS['BUSY']:
|
|
||||||
invoke_in_main_thread(tray_notification, friend.name, message.decode('utf8'), tray, window)
|
|
||||||
if settings['sound_notifications'] and profile.status != TOX_USER_STATUS['BUSY']:
|
|
||||||
sound_notification(SOUND_NOTIFICATION['MESSAGE'])
|
|
||||||
return wrapped
|
|
||||||
|
|
||||||
|
|
||||||
def friend_request(tox, public_key, message, message_size, user_data):
|
|
||||||
"""
|
|
||||||
Called when user get new friend request
|
|
||||||
"""
|
|
||||||
print 'Friend request'
|
|
||||||
profile = Profile.get_instance()
|
|
||||||
key = ''.join(chr(x) for x in public_key[:TOX_PUBLIC_KEY_SIZE])
|
|
||||||
tox_id = bin_to_string(key, TOX_PUBLIC_KEY_SIZE)
|
|
||||||
if tox_id not in Settings.get_instance()['blocked']:
|
|
||||||
invoke_in_main_thread(profile.process_friend_request, tox_id, message.decode('utf-8'))
|
|
||||||
|
|
||||||
|
|
||||||
def friend_typing(tox, friend_number, typing, user_data):
|
|
||||||
invoke_in_main_thread(Profile.get_instance().friend_typing, friend_number, typing)
|
|
||||||
|
|
||||||
# -----------------------------------------------------------------------------------------------------------------
|
|
||||||
# Callbacks - file transfers
|
|
||||||
# -----------------------------------------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
|
|
||||||
def tox_file_recv(window, tray):
|
|
||||||
"""
|
|
||||||
New incoming file
|
|
||||||
"""
|
|
||||||
def wrapped(tox, friend_number, file_number, file_type, size, file_name, file_name_size, user_data):
|
|
||||||
profile = Profile.get_instance()
|
|
||||||
settings = Settings.get_instance()
|
|
||||||
if file_type == TOX_FILE_KIND['DATA']:
|
|
||||||
print 'file'
|
|
||||||
try:
|
|
||||||
file_name = unicode(file_name[:file_name_size].decode('utf-8'))
|
|
||||||
except:
|
|
||||||
file_name = u'toxygen_file'
|
|
||||||
invoke_in_main_thread(profile.incoming_file_transfer,
|
|
||||||
friend_number,
|
|
||||||
file_number,
|
|
||||||
size,
|
|
||||||
file_name)
|
|
||||||
if not window.isActiveWindow():
|
|
||||||
friend = profile.get_friend_by_number(friend_number)
|
|
||||||
if settings['notifications'] and profile.status != TOX_USER_STATUS['BUSY']:
|
|
||||||
invoke_in_main_thread(tray_notification, 'File from ' + friend.name, file_name, tray, window)
|
|
||||||
if settings['sound_notifications'] and profile.status != TOX_USER_STATUS['BUSY']:
|
|
||||||
sound_notification(SOUND_NOTIFICATION['FILE_TRANSFER'])
|
|
||||||
else: # AVATAR
|
|
||||||
print 'Avatar'
|
|
||||||
invoke_in_main_thread(profile.incoming_avatar,
|
|
||||||
friend_number,
|
|
||||||
file_number,
|
|
||||||
size)
|
|
||||||
return wrapped
|
|
||||||
|
|
||||||
|
|
||||||
def file_recv_chunk(tox, friend_number, file_number, position, chunk, length, user_data):
|
|
||||||
"""
|
|
||||||
Incoming chunk
|
|
||||||
"""
|
|
||||||
if not length:
|
|
||||||
invoke_in_main_thread(Profile.get_instance().incoming_chunk,
|
|
||||||
friend_number,
|
|
||||||
file_number,
|
|
||||||
position,
|
|
||||||
None)
|
|
||||||
else:
|
|
||||||
Profile.get_instance().incoming_chunk(friend_number, file_number, position,chunk[:length])
|
|
||||||
|
|
||||||
|
|
||||||
def file_chunk_request(tox, friend_number, file_number, position, size, user_data):
|
|
||||||
"""
|
|
||||||
Outgoing chunk
|
|
||||||
"""
|
|
||||||
if size:
|
|
||||||
Profile.get_instance().outgoing_chunk(friend_number, file_number, position, size)
|
|
||||||
else:
|
|
||||||
invoke_in_main_thread(Profile.get_instance().outgoing_chunk,
|
|
||||||
friend_number,
|
|
||||||
file_number,
|
|
||||||
position,
|
|
||||||
size)
|
|
||||||
|
|
||||||
|
|
||||||
def file_recv_control(tox, friend_number, file_number, file_control, user_data):
|
|
||||||
"""
|
|
||||||
Friend cancelled, paused or resumed file transfer
|
|
||||||
"""
|
|
||||||
if file_control == TOX_FILE_CONTROL['CANCEL']:
|
|
||||||
Profile.get_instance().cancel_transfer(friend_number, file_number, True)
|
|
||||||
elif file_control == TOX_FILE_CONTROL['PAUSE']:
|
|
||||||
Profile.get_instance().pause_transfer(friend_number, file_number, True)
|
|
||||||
elif file_control == TOX_FILE_CONTROL['RESUME']:
|
|
||||||
Profile.get_instance().resume_transfer(friend_number, file_number, True)
|
|
||||||
|
|
||||||
# -----------------------------------------------------------------------------------------------------------------
|
|
||||||
# Callbacks - custom packets
|
|
||||||
# -----------------------------------------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
|
|
||||||
def lossless_packet(tox, friend_number, data, length, user_data):
|
|
||||||
"""
|
|
||||||
Incoming lossless packet
|
|
||||||
"""
|
|
||||||
plugin = PluginLoader.get_instance()
|
|
||||||
invoke_in_main_thread(plugin.callback_lossless, friend_number, data, length)
|
|
||||||
|
|
||||||
|
|
||||||
def lossy_packet(tox, friend_number, data, length, user_data):
|
|
||||||
"""
|
|
||||||
Incoming lossy packet
|
|
||||||
"""
|
|
||||||
plugin = PluginLoader.get_instance()
|
|
||||||
invoke_in_main_thread(plugin.callback_lossy, friend_number, data, length)
|
|
||||||
|
|
||||||
|
|
||||||
# -----------------------------------------------------------------------------------------------------------------
|
|
||||||
# Callbacks - audio
|
|
||||||
# -----------------------------------------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
def call_state(toxav, friend_number, mask, user_data):
|
|
||||||
"""
|
|
||||||
New call state
|
|
||||||
"""
|
|
||||||
print friend_number, mask
|
|
||||||
if mask == TOXAV_FRIEND_CALL_STATE['FINISHED'] or mask == TOXAV_FRIEND_CALL_STATE['ERROR']:
|
|
||||||
invoke_in_main_thread(Profile.get_instance().stop_call, friend_number, True)
|
|
||||||
else:
|
|
||||||
Profile.get_instance().call.toxav_call_state_cb(friend_number, mask)
|
|
||||||
|
|
||||||
|
|
||||||
def call(toxav, friend_number, audio, video, user_data):
|
|
||||||
"""
|
|
||||||
Incoming call from friend
|
|
||||||
"""
|
|
||||||
print friend_number, audio, video
|
|
||||||
invoke_in_main_thread(Profile.get_instance().incoming_call, audio, video, friend_number)
|
|
||||||
|
|
||||||
|
|
||||||
def callback_audio(toxav, friend_number, samples, audio_samples_per_channel, audio_channels_count, rate, user_data):
|
|
||||||
"""
|
|
||||||
New audio chunk
|
|
||||||
"""
|
|
||||||
print audio_samples_per_channel, audio_channels_count, rate
|
|
||||||
Profile.get_instance().call.chunk(
|
|
||||||
''.join(chr(x) for x in samples[:audio_samples_per_channel * 2 * audio_channels_count]),
|
|
||||||
audio_channels_count,
|
|
||||||
rate)
|
|
||||||
|
|
||||||
|
|
||||||
# -----------------------------------------------------------------------------------------------------------------
|
|
||||||
# Callbacks - initialization
|
|
||||||
# -----------------------------------------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
|
|
||||||
def init_callbacks(tox, window, tray):
|
|
||||||
"""
|
|
||||||
Initialization of all callbacks.
|
|
||||||
:param tox: tox instance
|
|
||||||
:param window: main window
|
|
||||||
:param tray: tray (for notifications)
|
|
||||||
"""
|
|
||||||
tox.callback_self_connection_status(self_connection_status(tox), 0)
|
|
||||||
|
|
||||||
tox.callback_friend_status(friend_status, 0)
|
|
||||||
tox.callback_friend_message(friend_message(window, tray), 0)
|
|
||||||
tox.callback_friend_connection_status(friend_connection_status, 0)
|
|
||||||
tox.callback_friend_name(friend_name, 0)
|
|
||||||
tox.callback_friend_status_message(friend_status_message, 0)
|
|
||||||
tox.callback_friend_request(friend_request, 0)
|
|
||||||
tox.callback_friend_typing(friend_typing, 0)
|
|
||||||
|
|
||||||
tox.callback_file_recv(tox_file_recv(window, tray), 0)
|
|
||||||
tox.callback_file_recv_chunk(file_recv_chunk, 0)
|
|
||||||
tox.callback_file_chunk_request(file_chunk_request, 0)
|
|
||||||
tox.callback_file_recv_control(file_recv_control, 0)
|
|
||||||
|
|
||||||
toxav = tox.AV
|
|
||||||
toxav.callback_call_state(call_state, 0)
|
|
||||||
toxav.callback_call(call, 0)
|
|
||||||
toxav.callback_audio_receive_frame(callback_audio, 0)
|
|
||||||
|
|
||||||
tox.callback_friend_lossless_packet(lossless_packet, 0)
|
|
||||||
tox.callback_friend_lossy_packet(lossy_packet, 0)
|
|
||||||
|
|
143
src/calls.py
@ -1,143 +0,0 @@
|
|||||||
import pyaudio
|
|
||||||
import time
|
|
||||||
import threading
|
|
||||||
import settings
|
|
||||||
from toxav_enums import *
|
|
||||||
# TODO: play sound until outgoing call will be started or cancelled and add timeout
|
|
||||||
|
|
||||||
CALL_TYPE = {
|
|
||||||
'NONE': 0,
|
|
||||||
'AUDIO': 1,
|
|
||||||
'VIDEO': 2
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
class AV(object):
|
|
||||||
|
|
||||||
def __init__(self, toxav):
|
|
||||||
self._toxav = toxav
|
|
||||||
self._running = True
|
|
||||||
|
|
||||||
self._calls = {} # dict: key - friend number, value - call type
|
|
||||||
|
|
||||||
self._audio = None
|
|
||||||
self._audio_stream = None
|
|
||||||
self._audio_thread = None
|
|
||||||
self._audio_running = False
|
|
||||||
self._out_stream = None
|
|
||||||
|
|
||||||
self._audio_rate = 8000
|
|
||||||
self._audio_channels = 1
|
|
||||||
self._audio_duration = 60
|
|
||||||
self._audio_sample_count = self._audio_rate * self._audio_channels * self._audio_duration / 1000
|
|
||||||
|
|
||||||
def __contains__(self, friend_number):
|
|
||||||
return friend_number in self._calls
|
|
||||||
|
|
||||||
def __call__(self, friend_number, audio, video):
|
|
||||||
"""Call friend with specified number"""
|
|
||||||
self._toxav.call(friend_number, 32 if audio else 0, 5000 if video else 0)
|
|
||||||
self._calls[friend_number] = CALL_TYPE['AUDIO']
|
|
||||||
self.start_audio_thread()
|
|
||||||
|
|
||||||
def finish_call(self, friend_number, by_friend=False):
|
|
||||||
|
|
||||||
if not by_friend:
|
|
||||||
self._toxav.call_control(friend_number, TOXAV_CALL_CONTROL['CANCEL'])
|
|
||||||
if friend_number in self._calls:
|
|
||||||
del self._calls[friend_number]
|
|
||||||
if not len(self._calls):
|
|
||||||
self.stop_audio_thread()
|
|
||||||
|
|
||||||
def stop(self):
|
|
||||||
self._running = False
|
|
||||||
self.stop_audio_thread()
|
|
||||||
|
|
||||||
def start_audio_thread(self):
|
|
||||||
"""
|
|
||||||
Start audio sending
|
|
||||||
"""
|
|
||||||
if self._audio_thread is not None:
|
|
||||||
return
|
|
||||||
|
|
||||||
self._audio_running = True
|
|
||||||
|
|
||||||
self._audio = pyaudio.PyAudio()
|
|
||||||
self._audio_stream = self._audio.open(format=pyaudio.paInt16,
|
|
||||||
rate=self._audio_rate,
|
|
||||||
channels=self._audio_channels,
|
|
||||||
input=True,
|
|
||||||
input_device_index=settings.Settings.get_instance().audio['input'],
|
|
||||||
frames_per_buffer=self._audio_sample_count * 10)
|
|
||||||
|
|
||||||
self._audio_thread = threading.Thread(target=self.send_audio)
|
|
||||||
self._audio_thread.start()
|
|
||||||
|
|
||||||
def stop_audio_thread(self):
|
|
||||||
|
|
||||||
if self._audio_thread is None:
|
|
||||||
return
|
|
||||||
|
|
||||||
self._audio_running = False
|
|
||||||
|
|
||||||
self._audio_thread.join()
|
|
||||||
|
|
||||||
self._audio_thread = None
|
|
||||||
self._audio_stream = None
|
|
||||||
self._audio = None
|
|
||||||
|
|
||||||
if self._out_stream is not None:
|
|
||||||
self._out_stream.stop_stream()
|
|
||||||
self._out_stream.close()
|
|
||||||
self._out_stream = None
|
|
||||||
|
|
||||||
def chunk(self, samples, channels_count, rate):
|
|
||||||
"""
|
|
||||||
Incoming chunk
|
|
||||||
"""
|
|
||||||
|
|
||||||
if self._out_stream is None:
|
|
||||||
self._out_stream = self._audio.open(format=pyaudio.paInt16,
|
|
||||||
channels=channels_count,
|
|
||||||
rate=rate,
|
|
||||||
output_device_index=settings.Settings.get_instance().audio['output'],
|
|
||||||
output=True)
|
|
||||||
self._out_stream.write(samples)
|
|
||||||
|
|
||||||
def send_audio(self):
|
|
||||||
"""
|
|
||||||
This method sends audio to friends
|
|
||||||
"""
|
|
||||||
|
|
||||||
while self._audio_running:
|
|
||||||
try:
|
|
||||||
pcm = self._audio_stream.read(self._audio_sample_count)
|
|
||||||
if pcm:
|
|
||||||
for friend in self._calls:
|
|
||||||
if self._calls[friend] & 1:
|
|
||||||
try:
|
|
||||||
self._toxav.audio_send_frame(friend, pcm, self._audio_sample_count,
|
|
||||||
self._audio_channels, self._audio_rate)
|
|
||||||
except:
|
|
||||||
pass
|
|
||||||
except:
|
|
||||||
pass
|
|
||||||
|
|
||||||
time.sleep(0.01)
|
|
||||||
|
|
||||||
def accept_call(self, friend_number, audio_enabled, video_enabled):
|
|
||||||
|
|
||||||
if self._running:
|
|
||||||
self._calls[friend_number] = int(video_enabled) * 2 + int(audio_enabled)
|
|
||||||
self._toxav.answer(friend_number, 32 if audio_enabled else 0, 5000 if video_enabled else 0)
|
|
||||||
self.start_audio_thread()
|
|
||||||
|
|
||||||
def toxav_call_state_cb(self, friend_number, state):
|
|
||||||
"""
|
|
||||||
New call state
|
|
||||||
"""
|
|
||||||
if self._running:
|
|
||||||
|
|
||||||
if state & TOXAV_FRIEND_CALL_STATE['ACCEPTING_A']:
|
|
||||||
self._calls[friend_number] |= 1
|
|
||||||
|
|
@ -1,272 +0,0 @@
|
|||||||
from toxcore_enums_and_consts import TOX_FILE_KIND, TOX_FILE_CONTROL
|
|
||||||
from os.path import basename, getsize, exists
|
|
||||||
from os import remove, rename
|
|
||||||
from time import time, sleep
|
|
||||||
from tox import Tox
|
|
||||||
import settings
|
|
||||||
try:
|
|
||||||
from PySide import QtCore
|
|
||||||
except ImportError:
|
|
||||||
from PyQt4 import QtCore
|
|
||||||
|
|
||||||
|
|
||||||
TOX_FILE_TRANSFER_STATE = {
|
|
||||||
'RUNNING': 0,
|
|
||||||
'PAUSED': 1,
|
|
||||||
'CANCELED': 2,
|
|
||||||
'FINISHED': 3,
|
|
||||||
'PAUSED_BY_FRIEND': 4
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
class StateSignal(QtCore.QObject):
|
|
||||||
try:
|
|
||||||
signal = QtCore.Signal(int, float) # state and progress
|
|
||||||
except:
|
|
||||||
signal = QtCore.pyqtSignal(int, float) # state and progress - pyqt4
|
|
||||||
|
|
||||||
|
|
||||||
class FileTransfer(QtCore.QObject):
|
|
||||||
"""
|
|
||||||
Superclass for file transfers
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self, path, tox, friend_number, size, file_number=None):
|
|
||||||
QtCore.QObject.__init__(self)
|
|
||||||
self._path = path
|
|
||||||
self._tox = tox
|
|
||||||
self._friend_number = friend_number
|
|
||||||
self.state = TOX_FILE_TRANSFER_STATE['RUNNING']
|
|
||||||
self._file_number = file_number
|
|
||||||
self._creation_time = time()
|
|
||||||
self._size = float(size)
|
|
||||||
self._done = 0
|
|
||||||
self._state_changed = StateSignal()
|
|
||||||
|
|
||||||
def set_tox(self, tox):
|
|
||||||
self._tox = tox
|
|
||||||
|
|
||||||
def set_state_changed_handler(self, handler):
|
|
||||||
self._state_changed.signal.connect(handler)
|
|
||||||
|
|
||||||
def signal(self):
|
|
||||||
self._state_changed.signal.emit(self.state, self._done / self._size if self._size else 0)
|
|
||||||
|
|
||||||
def get_file_number(self):
|
|
||||||
return self._file_number
|
|
||||||
|
|
||||||
def get_friend_number(self):
|
|
||||||
return self._friend_number
|
|
||||||
|
|
||||||
def cancel(self):
|
|
||||||
self.send_control(TOX_FILE_CONTROL['CANCEL'])
|
|
||||||
if hasattr(self, '_file'):
|
|
||||||
self._file.close()
|
|
||||||
self._state_changed.signal.emit(self.state, 1)
|
|
||||||
|
|
||||||
def cancelled(self):
|
|
||||||
if hasattr(self, '_file'):
|
|
||||||
sleep(0.1)
|
|
||||||
self._file.close()
|
|
||||||
self.state = TOX_FILE_TRANSFER_STATE['CANCELED']
|
|
||||||
self._state_changed.signal.emit(self.state, 1)
|
|
||||||
|
|
||||||
def pause(self, by_friend):
|
|
||||||
if not by_friend:
|
|
||||||
self.send_control(TOX_FILE_CONTROL['PAUSE'])
|
|
||||||
else:
|
|
||||||
self.state = TOX_FILE_TRANSFER_STATE['PAUSED_BY_FRIEND']
|
|
||||||
self.signal()
|
|
||||||
|
|
||||||
def send_control(self, control):
|
|
||||||
if self._tox.file_control(self._friend_number, self._file_number, control):
|
|
||||||
self.state = control
|
|
||||||
self.signal()
|
|
||||||
|
|
||||||
def get_file_id(self):
|
|
||||||
return self._tox.file_get_file_id(self._friend_number, self._file_number)
|
|
||||||
|
|
||||||
# -----------------------------------------------------------------------------------------------------------------
|
|
||||||
# Send file
|
|
||||||
# -----------------------------------------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
|
|
||||||
class SendTransfer(FileTransfer):
|
|
||||||
|
|
||||||
def __init__(self, path, tox, friend_number, kind=TOX_FILE_KIND['DATA'], file_id=None):
|
|
||||||
if path is not None:
|
|
||||||
self._file = open(path, 'rb')
|
|
||||||
size = getsize(path)
|
|
||||||
else:
|
|
||||||
size = 0
|
|
||||||
super(SendTransfer, self).__init__(path, tox, friend_number, size)
|
|
||||||
self._file_number = tox.file_send(friend_number, kind, size, file_id,
|
|
||||||
basename(path).encode('utf-8') if path else '')
|
|
||||||
|
|
||||||
def send_chunk(self, position, size):
|
|
||||||
"""
|
|
||||||
Send chunk
|
|
||||||
:param position: start position in file
|
|
||||||
:param size: chunk max size
|
|
||||||
"""
|
|
||||||
if size:
|
|
||||||
self._file.seek(position)
|
|
||||||
data = self._file.read(size)
|
|
||||||
self._tox.file_send_chunk(self._friend_number, self._file_number, position, data)
|
|
||||||
self._done += size
|
|
||||||
self.signal()
|
|
||||||
else:
|
|
||||||
if hasattr(self, '_file'):
|
|
||||||
self._file.close()
|
|
||||||
self.state = TOX_FILE_TRANSFER_STATE['FINISHED']
|
|
||||||
self._state_changed.signal.emit(self.state, 1)
|
|
||||||
|
|
||||||
|
|
||||||
class SendAvatar(SendTransfer):
|
|
||||||
"""
|
|
||||||
Send avatar to friend. Doesn't need file transfer item
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self, path, tox, friend_number):
|
|
||||||
if path is None:
|
|
||||||
hash = None
|
|
||||||
else:
|
|
||||||
with open(path, 'rb') as fl:
|
|
||||||
hash = Tox.hash(fl.read())
|
|
||||||
super(SendAvatar, self).__init__(path, tox, friend_number, TOX_FILE_KIND['AVATAR'], hash)
|
|
||||||
|
|
||||||
|
|
||||||
class SendFromBuffer(FileTransfer):
|
|
||||||
"""
|
|
||||||
Send inline image
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self, tox, friend_number, data, file_name):
|
|
||||||
super(SendFromBuffer, self).__init__(None, tox, friend_number, len(data))
|
|
||||||
self._data = data
|
|
||||||
self._file_number = tox.file_send(friend_number, TOX_FILE_KIND['DATA'], len(data), None, file_name)
|
|
||||||
|
|
||||||
def get_data(self):
|
|
||||||
return self._data
|
|
||||||
|
|
||||||
def send_chunk(self, position, size):
|
|
||||||
if size:
|
|
||||||
data = self._data[position:position + size]
|
|
||||||
self._tox.file_send_chunk(self._friend_number, self._file_number, position, data)
|
|
||||||
self._done += size
|
|
||||||
self._state_changed.signal.emit(self.state, self._done / self._size)
|
|
||||||
else:
|
|
||||||
self.state = TOX_FILE_TRANSFER_STATE['FINISHED']
|
|
||||||
self._state_changed.signal.emit(self.state, 1)
|
|
||||||
|
|
||||||
# -----------------------------------------------------------------------------------------------------------------
|
|
||||||
# Receive file
|
|
||||||
# -----------------------------------------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
|
|
||||||
class ReceiveTransfer(FileTransfer):
|
|
||||||
|
|
||||||
def __init__(self, path, tox, friend_number, size, file_number):
|
|
||||||
super(ReceiveTransfer, self).__init__(path, tox, friend_number, size, file_number)
|
|
||||||
self._file = open(self._path, 'wb')
|
|
||||||
self._file.truncate(0)
|
|
||||||
self._file_size = 0
|
|
||||||
|
|
||||||
def cancel(self):
|
|
||||||
super(ReceiveTransfer, self).cancel()
|
|
||||||
remove(self._path)
|
|
||||||
|
|
||||||
def write_chunk(self, position, data):
|
|
||||||
"""
|
|
||||||
Incoming chunk
|
|
||||||
:param position: position in file to save data
|
|
||||||
:param data: raw data (string)
|
|
||||||
"""
|
|
||||||
if data is None:
|
|
||||||
self._file.close()
|
|
||||||
self.state = TOX_FILE_TRANSFER_STATE['FINISHED']
|
|
||||||
self._state_changed.signal.emit(self.state, 1)
|
|
||||||
else:
|
|
||||||
data = ''.join(chr(x) for x in data)
|
|
||||||
if self._file_size < position:
|
|
||||||
self._file.seek(0, 2)
|
|
||||||
self._file.write('\0' * (position - self._file_size))
|
|
||||||
self._file.seek(position)
|
|
||||||
self._file.write(data)
|
|
||||||
self._file.flush()
|
|
||||||
l = len(data)
|
|
||||||
if position + l > self._file_size:
|
|
||||||
self._file_size = position + l
|
|
||||||
self._done += l
|
|
||||||
self._state_changed.signal.emit(self.state, self._done / self._size)
|
|
||||||
|
|
||||||
|
|
||||||
class ReceiveToBuffer(FileTransfer):
|
|
||||||
"""
|
|
||||||
Inline image - save in buffer not in file system
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self, tox, friend_number, size, file_number):
|
|
||||||
super(ReceiveToBuffer, self).__init__(None, tox, friend_number, size, file_number)
|
|
||||||
self._data = ''
|
|
||||||
self._data_size = 0
|
|
||||||
|
|
||||||
def get_data(self):
|
|
||||||
return self._data
|
|
||||||
|
|
||||||
def write_chunk(self, position, data):
|
|
||||||
if data is None:
|
|
||||||
self.state = TOX_FILE_TRANSFER_STATE['FINISHED']
|
|
||||||
self._state_changed.signal.emit(self.state, 1)
|
|
||||||
else:
|
|
||||||
data = ''.join(chr(x) for x in data)
|
|
||||||
l = len(data)
|
|
||||||
if self._data_size < position:
|
|
||||||
self._data += ('\0' * (position - self._data_size))
|
|
||||||
self._data = self._data[:position] + data + self._data[position + l:]
|
|
||||||
if position + l > self._data_size:
|
|
||||||
self._data_size = position + l
|
|
||||||
self._done += l
|
|
||||||
self.signal()
|
|
||||||
|
|
||||||
|
|
||||||
class ReceiveAvatar(ReceiveTransfer):
|
|
||||||
"""
|
|
||||||
Get friend's avatar. Doesn't need file transfer item
|
|
||||||
"""
|
|
||||||
MAX_AVATAR_SIZE = 512 * 1024
|
|
||||||
|
|
||||||
def __init__(self, tox, friend_number, size, file_number):
|
|
||||||
path = settings.ProfileHelper.get_path() + 'avatars/{}.png'.format(tox.friend_get_public_key(friend_number))
|
|
||||||
super(ReceiveAvatar, self).__init__(path + '.tmp', tox, friend_number, size, file_number)
|
|
||||||
if size > self.MAX_AVATAR_SIZE:
|
|
||||||
self.send_control(TOX_FILE_CONTROL['CANCEL'])
|
|
||||||
remove(path + '.tmp')
|
|
||||||
elif not size:
|
|
||||||
self.send_control(TOX_FILE_CONTROL['CANCEL'])
|
|
||||||
self._file.close()
|
|
||||||
if exists(path):
|
|
||||||
remove(path)
|
|
||||||
remove(path + '.tmp')
|
|
||||||
elif exists(path):
|
|
||||||
hash = self.get_file_id()
|
|
||||||
with open(path) as fl:
|
|
||||||
data = fl.read()
|
|
||||||
existing_hash = Tox.hash(data)
|
|
||||||
if hash == existing_hash:
|
|
||||||
self.send_control(TOX_FILE_CONTROL['CANCEL'])
|
|
||||||
remove(path + '.tmp')
|
|
||||||
else:
|
|
||||||
self.send_control(TOX_FILE_CONTROL['RESUME'])
|
|
||||||
else:
|
|
||||||
self.send_control(TOX_FILE_CONTROL['RESUME'])
|
|
||||||
|
|
||||||
def write_chunk(self, position, data):
|
|
||||||
super(ReceiveAvatar, self).write_chunk(position, data)
|
|
||||||
if self.state:
|
|
||||||
avatar_path = self._path[:-4]
|
|
||||||
if exists(avatar_path):
|
|
||||||
remove(avatar_path)
|
|
||||||
rename(self._path, avatar_path)
|
|
||||||
|
|
||||||
|
|
147
src/history.py
@ -1,147 +0,0 @@
|
|||||||
# coding=utf-8
|
|
||||||
from sqlite3 import connect
|
|
||||||
import settings
|
|
||||||
from os import chdir
|
|
||||||
import os.path
|
|
||||||
from toxencryptsave import LibToxEncryptSave
|
|
||||||
|
|
||||||
|
|
||||||
PAGE_SIZE = 42
|
|
||||||
|
|
||||||
MESSAGE_OWNER = {
|
|
||||||
'ME': 0,
|
|
||||||
'FRIEND': 1
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
class History(object):
|
|
||||||
|
|
||||||
def __init__(self, name):
|
|
||||||
self._name = name
|
|
||||||
chdir(settings.ProfileHelper.get_path())
|
|
||||||
path = settings.ProfileHelper.get_path() + self._name + '.hstr'
|
|
||||||
if os.path.exists(path):
|
|
||||||
decr = LibToxEncryptSave.get_instance()
|
|
||||||
with open(path, 'rb') as fin:
|
|
||||||
data = fin.read()
|
|
||||||
if decr.is_data_encrypted(data):
|
|
||||||
data = decr.pass_decrypt(data)
|
|
||||||
with open(path, 'wb') as fout:
|
|
||||||
fout.write(data)
|
|
||||||
db = connect(name + '.hstr')
|
|
||||||
cursor = db.cursor()
|
|
||||||
cursor.execute('CREATE TABLE IF NOT EXISTS friends('
|
|
||||||
' tox_id TEXT PRIMARY KEY'
|
|
||||||
')')
|
|
||||||
db.close()
|
|
||||||
|
|
||||||
def save(self):
|
|
||||||
encr = LibToxEncryptSave.get_instance()
|
|
||||||
if encr.has_password():
|
|
||||||
path = settings.ProfileHelper.get_path() + self._name + '.hstr'
|
|
||||||
with open(path, 'rb') as fin:
|
|
||||||
data = fin.read()
|
|
||||||
data = encr.pass_encrypt(data)
|
|
||||||
with open(path, 'wb') as fout:
|
|
||||||
fout.write(data)
|
|
||||||
|
|
||||||
def export(self, directory):
|
|
||||||
path = settings.ProfileHelper.get_path() + self._name + '.hstr'
|
|
||||||
new_path = directory + self._name + '.hstr'
|
|
||||||
with open(path, 'rb') as fin:
|
|
||||||
data = fin.read()
|
|
||||||
with open(new_path, 'wb') as fout:
|
|
||||||
fout.write(data)
|
|
||||||
|
|
||||||
def add_friend_to_db(self, tox_id):
|
|
||||||
chdir(settings.ProfileHelper.get_path())
|
|
||||||
db = connect(self._name + '.hstr')
|
|
||||||
try:
|
|
||||||
cursor = db.cursor()
|
|
||||||
cursor.execute('INSERT INTO friends VALUES (?);', (tox_id, ))
|
|
||||||
cursor.execute('CREATE TABLE id' + tox_id + '('
|
|
||||||
' id INTEGER PRIMARY KEY,'
|
|
||||||
' message TEXT,'
|
|
||||||
' owner INTEGER,'
|
|
||||||
' unix_time REAL,'
|
|
||||||
' message_type INTEGER'
|
|
||||||
')')
|
|
||||||
db.commit()
|
|
||||||
except:
|
|
||||||
db.rollback()
|
|
||||||
raise
|
|
||||||
finally:
|
|
||||||
db.close()
|
|
||||||
|
|
||||||
def delete_friend_from_db(self, tox_id):
|
|
||||||
chdir(settings.ProfileHelper.get_path())
|
|
||||||
db = connect(self._name + '.hstr')
|
|
||||||
try:
|
|
||||||
cursor = db.cursor()
|
|
||||||
cursor.execute('DELETE FROM friends WHERE tox_id=?;', (tox_id, ))
|
|
||||||
cursor.execute('DROP TABLE id' + tox_id + ';')
|
|
||||||
db.commit()
|
|
||||||
except:
|
|
||||||
db.rollback()
|
|
||||||
raise
|
|
||||||
finally:
|
|
||||||
db.close()
|
|
||||||
|
|
||||||
def friend_exists_in_db(self, tox_id):
|
|
||||||
chdir(settings.ProfileHelper.get_path())
|
|
||||||
db = connect(self._name + '.hstr')
|
|
||||||
cursor = db.cursor()
|
|
||||||
cursor.execute('SELECT 0 FROM friends WHERE tox_id=?', (tox_id, ))
|
|
||||||
result = cursor.fetchone()
|
|
||||||
db.close()
|
|
||||||
return result is not None
|
|
||||||
|
|
||||||
def save_messages_to_db(self, tox_id, messages_iter):
|
|
||||||
chdir(settings.ProfileHelper.get_path())
|
|
||||||
db = connect(self._name + '.hstr')
|
|
||||||
try:
|
|
||||||
cursor = db.cursor()
|
|
||||||
cursor.executemany('INSERT INTO id' + tox_id + '(message, owner, unix_time, message_type) '
|
|
||||||
'VALUES (?, ?, ?, ?);', messages_iter)
|
|
||||||
db.commit()
|
|
||||||
except:
|
|
||||||
db.rollback()
|
|
||||||
raise
|
|
||||||
finally:
|
|
||||||
db.close()
|
|
||||||
|
|
||||||
def delete_messages(self, tox_id):
|
|
||||||
chdir(settings.ProfileHelper.get_path())
|
|
||||||
db = connect(self._name + '.hstr')
|
|
||||||
try:
|
|
||||||
cursor = db.cursor()
|
|
||||||
cursor.execute('DELETE FROM id' + tox_id + ';')
|
|
||||||
db.commit()
|
|
||||||
except:
|
|
||||||
db.rollback()
|
|
||||||
raise
|
|
||||||
finally:
|
|
||||||
db.close()
|
|
||||||
|
|
||||||
def messages_getter(self, tox_id):
|
|
||||||
return History.MessageGetter(self._name, tox_id)
|
|
||||||
|
|
||||||
class MessageGetter(object):
|
|
||||||
def __init__(self, name, tox_id):
|
|
||||||
chdir(settings.ProfileHelper.get_path())
|
|
||||||
self._db = connect(name + '.hstr')
|
|
||||||
self._cursor = self._db.cursor()
|
|
||||||
self._cursor.execute('SELECT message, owner, unix_time, message_type FROM id' + tox_id +
|
|
||||||
' ORDER BY unix_time DESC;')
|
|
||||||
|
|
||||||
def get_one(self):
|
|
||||||
return self._cursor.fetchone()
|
|
||||||
|
|
||||||
def get_all(self):
|
|
||||||
return self._cursor.fetchall()
|
|
||||||
|
|
||||||
def get(self, count):
|
|
||||||
return self._cursor.fetchmany(count)
|
|
||||||
|
|
||||||
def __del__(self):
|
|
||||||
self._db.close()
|
|
Before Width: | Height: | Size: 114 KiB |
Before Width: | Height: | Size: 13 KiB |
Before Width: | Height: | Size: 13 KiB |
Before Width: | Height: | Size: 3.9 KiB |
Before Width: | Height: | Size: 3.5 KiB |
Before Width: | Height: | Size: 118 KiB |
Before Width: | Height: | Size: 12 KiB |
Before Width: | Height: | Size: 3.4 KiB |
Before Width: | Height: | Size: 3.5 KiB |
Before Width: | Height: | Size: 2.5 KiB |
Before Width: | Height: | Size: 3.5 KiB |
Before Width: | Height: | Size: 306 B |
Before Width: | Height: | Size: 2.6 KiB |
Before Width: | Height: | Size: 481 B |
Before Width: | Height: | Size: 3.6 KiB |
Before Width: | Height: | Size: 1.1 KiB |
Before Width: | Height: | Size: 5.6 KiB |
@ -1,49 +0,0 @@
|
|||||||
from platform import system
|
|
||||||
from ctypes import CDLL
|
|
||||||
|
|
||||||
|
|
||||||
class LibToxCore(object):
|
|
||||||
|
|
||||||
def __init__(self):
|
|
||||||
if system() == 'Linux':
|
|
||||||
# libtoxcore and libsodium must be installed in your os
|
|
||||||
self._libtoxcore = CDLL('libtoxcore.so')
|
|
||||||
elif system() == 'Windows':
|
|
||||||
self._libtoxcore = CDLL('libs/libtox.dll')
|
|
||||||
else:
|
|
||||||
raise OSError('Unknown system.')
|
|
||||||
|
|
||||||
def __getattr__(self, item):
|
|
||||||
return self._libtoxcore.__getattr__(item)
|
|
||||||
|
|
||||||
|
|
||||||
class LibToxAV(object):
|
|
||||||
|
|
||||||
def __init__(self):
|
|
||||||
if system() == 'Linux':
|
|
||||||
# that /usr/lib/libtoxav.so must exists
|
|
||||||
self._libtoxav = CDLL('libtoxav.so')
|
|
||||||
elif system() == 'Windows':
|
|
||||||
# on Windows av api is in libtox.dll
|
|
||||||
self._libtoxav = CDLL('libs/libtox.dll')
|
|
||||||
else:
|
|
||||||
raise OSError('Unknown system.')
|
|
||||||
|
|
||||||
def __getattr__(self, item):
|
|
||||||
return self._libtoxav.__getattr__(item)
|
|
||||||
|
|
||||||
|
|
||||||
class LibToxEncryptSave(object):
|
|
||||||
|
|
||||||
def __init__(self):
|
|
||||||
if system() == 'Linux':
|
|
||||||
# /usr/lib/libtoxencryptsave.so must exists
|
|
||||||
self._lib_tox_encrypt_save = CDLL('libtoxencryptsave.so')
|
|
||||||
elif system() == 'Windows':
|
|
||||||
# on Windows profile encryption api is in libtox.dll
|
|
||||||
self._lib_tox_encrypt_save = CDLL('libs/libtox.dll')
|
|
||||||
else:
|
|
||||||
raise OSError('Unknown system.')
|
|
||||||
|
|
||||||
def __getattr__(self, item):
|
|
||||||
return self._lib_tox_encrypt_save.__getattr__(item)
|
|
@ -1,319 +0,0 @@
|
|||||||
from toxcore_enums_and_consts import *
|
|
||||||
try:
|
|
||||||
from PySide import QtCore, QtGui
|
|
||||||
except ImportError:
|
|
||||||
from PyQt4 import QtCore, QtGui
|
|
||||||
import profile
|
|
||||||
from file_transfers import TOX_FILE_TRANSFER_STATE
|
|
||||||
from util import curr_directory, convert_time
|
|
||||||
from messages import FILE_TRANSFER_MESSAGE_STATUS
|
|
||||||
from widgets import DataLabel
|
|
||||||
|
|
||||||
|
|
||||||
class MessageEdit(QtGui.QTextEdit):
|
|
||||||
|
|
||||||
def __init__(self, text, width, parent=None):
|
|
||||||
super(MessageEdit, self).__init__(parent)
|
|
||||||
self.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
|
|
||||||
self.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
|
|
||||||
self.setWordWrapMode(QtGui.QTextOption.WrapAtWordBoundaryOrAnywhere)
|
|
||||||
self.document().setTextWidth(width)
|
|
||||||
self.setPlainText(text)
|
|
||||||
font = QtGui.QFont()
|
|
||||||
font.setFamily("Times New Roman")
|
|
||||||
font.setPixelSize(14)
|
|
||||||
font.setBold(False)
|
|
||||||
self.setFont(font)
|
|
||||||
self.setFixedHeight(self.document().size().height())
|
|
||||||
self.setTextInteractionFlags(QtCore.Qt.TextSelectableByMouse | QtCore.Qt.LinksAccessibleByMouse)
|
|
||||||
|
|
||||||
|
|
||||||
class MessageItem(QtGui.QWidget):
|
|
||||||
"""
|
|
||||||
Message in messages list
|
|
||||||
"""
|
|
||||||
def __init__(self, text, time, user='', message_type=TOX_MESSAGE_TYPE['NORMAL'], parent=None):
|
|
||||||
QtGui.QWidget.__init__(self, parent)
|
|
||||||
self.name = DataLabel(self)
|
|
||||||
self.name.setGeometry(QtCore.QRect(0, 2, 95, 20))
|
|
||||||
self.name.setTextFormat(QtCore.Qt.PlainText)
|
|
||||||
font = QtGui.QFont()
|
|
||||||
font.setFamily("Times New Roman")
|
|
||||||
font.setPointSize(11)
|
|
||||||
font.setBold(True)
|
|
||||||
self.name.setFont(font)
|
|
||||||
self.name.setObjectName("name")
|
|
||||||
self.name.setText(user)
|
|
||||||
|
|
||||||
self.time = QtGui.QLabel(self)
|
|
||||||
self.time.setGeometry(QtCore.QRect(parent.width() - 50, 0, 50, 25))
|
|
||||||
font = QtGui.QFont()
|
|
||||||
font.setFamily("Times New Roman")
|
|
||||||
font.setPointSize(10)
|
|
||||||
font.setBold(False)
|
|
||||||
self.time.setFont(font)
|
|
||||||
self.time.setObjectName("time")
|
|
||||||
self.time.setText(time)
|
|
||||||
|
|
||||||
self.message = MessageEdit(text, parent.width() - 150, self)
|
|
||||||
self.message.setGeometry(QtCore.QRect(100, 0, parent.width() - 150, self.message.height()))
|
|
||||||
self.setFixedHeight(self.message.height())
|
|
||||||
|
|
||||||
if message_type == TOX_MESSAGE_TYPE['ACTION']:
|
|
||||||
self.name.setStyleSheet("QLabel { color: #4169E1; }")
|
|
||||||
self.message.setStyleSheet("QTextEdit { color: #4169E1; }")
|
|
||||||
else:
|
|
||||||
if text[0] == '>':
|
|
||||||
self.message.setStyleSheet("QTextEdit { color: green; }")
|
|
||||||
if text[-1] == '<':
|
|
||||||
self.message.setStyleSheet("QTextEdit { color: red; }")
|
|
||||||
|
|
||||||
|
|
||||||
class ContactItem(QtGui.QWidget):
|
|
||||||
"""
|
|
||||||
Contact in friends list
|
|
||||||
"""
|
|
||||||
def __init__(self, parent=None):
|
|
||||||
QtGui.QWidget.__init__(self, parent)
|
|
||||||
self.setBaseSize(QtCore.QSize(250, 70))
|
|
||||||
self.avatar_label = QtGui.QLabel(self)
|
|
||||||
self.avatar_label.setGeometry(QtCore.QRect(3, 3, 64, 64))
|
|
||||||
self.avatar_label.setScaledContents(True)
|
|
||||||
self.name = DataLabel(self)
|
|
||||||
self.name.setGeometry(QtCore.QRect(70, 10, 160, 25))
|
|
||||||
font = QtGui.QFont()
|
|
||||||
font.setFamily("Times New Roman")
|
|
||||||
font.setPointSize(12)
|
|
||||||
font.setBold(True)
|
|
||||||
self.name.setFont(font)
|
|
||||||
self.name.setObjectName("name")
|
|
||||||
self.status_message = DataLabel(self)
|
|
||||||
self.status_message.setGeometry(QtCore.QRect(70, 30, 180, 20))
|
|
||||||
font.setPointSize(10)
|
|
||||||
font.setBold(False)
|
|
||||||
self.status_message.setFont(font)
|
|
||||||
self.status_message.setObjectName("status_message")
|
|
||||||
self.connection_status = StatusCircle(self)
|
|
||||||
self.connection_status.setGeometry(QtCore.QRect(220, 5, 32, 32))
|
|
||||||
self.connection_status.setObjectName("connection_status")
|
|
||||||
|
|
||||||
|
|
||||||
class StatusCircle(QtGui.QWidget):
|
|
||||||
"""
|
|
||||||
Connection status
|
|
||||||
"""
|
|
||||||
def __init__(self, parent):
|
|
||||||
QtGui.QWidget.__init__(self, parent)
|
|
||||||
self.setGeometry(0, 0, 32, 32)
|
|
||||||
self.data = None
|
|
||||||
self.messages = False
|
|
||||||
|
|
||||||
def paintEvent(self, event):
|
|
||||||
paint = QtGui.QPainter()
|
|
||||||
paint.begin(self)
|
|
||||||
paint.setRenderHint(QtGui.QPainter.Antialiasing)
|
|
||||||
k = 16
|
|
||||||
rad_x = rad_y = 5
|
|
||||||
if self.data is None:
|
|
||||||
color = QtCore.Qt.transparent
|
|
||||||
else:
|
|
||||||
if self.data == TOX_USER_STATUS['NONE']:
|
|
||||||
color = QtGui.QColor(50, 205, 50)
|
|
||||||
elif self.data == TOX_USER_STATUS['AWAY']:
|
|
||||||
color = QtGui.QColor(255, 200, 50)
|
|
||||||
else: # self.data == TOX_USER_STATUS['BUSY']:
|
|
||||||
color = QtGui.QColor(255, 50, 0)
|
|
||||||
|
|
||||||
paint.setPen(color)
|
|
||||||
center = QtCore.QPoint(k, k)
|
|
||||||
paint.setBrush(color)
|
|
||||||
paint.drawEllipse(center, rad_x, rad_y)
|
|
||||||
if self.messages:
|
|
||||||
if color == QtCore.Qt.transparent:
|
|
||||||
color = QtCore.Qt.darkRed
|
|
||||||
paint.setBrush(QtCore.Qt.transparent)
|
|
||||||
paint.setPen(color)
|
|
||||||
paint.drawEllipse(center, rad_x + 3, rad_y + 3)
|
|
||||||
paint.end()
|
|
||||||
|
|
||||||
|
|
||||||
class FileTransferItem(QtGui.QListWidget):
|
|
||||||
|
|
||||||
def __init__(self, file_name, size, time, user, friend_number, file_number, state, width, parent=None):
|
|
||||||
|
|
||||||
QtGui.QListWidget.__init__(self, parent)
|
|
||||||
self.resize(QtCore.QSize(width, 34))
|
|
||||||
if state == FILE_TRANSFER_MESSAGE_STATUS['CANCELLED']:
|
|
||||||
self.setStyleSheet('QListWidget { border: 1px solid #B40404; }')
|
|
||||||
elif state in (FILE_TRANSFER_MESSAGE_STATUS['INCOMING_NOT_STARTED'], FILE_TRANSFER_MESSAGE_STATUS['PAUSED_BY_FRIEND']):
|
|
||||||
self.setStyleSheet('QListWidget { border: 1px solid #FF8000; }')
|
|
||||||
else:
|
|
||||||
self.setStyleSheet('QListWidget { border: 1px solid green; }')
|
|
||||||
|
|
||||||
self.state = state
|
|
||||||
|
|
||||||
self.name = DataLabel(self)
|
|
||||||
self.name.setGeometry(QtCore.QRect(3, 7, 95, 20))
|
|
||||||
self.name.setTextFormat(QtCore.Qt.PlainText)
|
|
||||||
font = QtGui.QFont()
|
|
||||||
font.setFamily("Times New Roman")
|
|
||||||
font.setPointSize(11)
|
|
||||||
font.setBold(True)
|
|
||||||
self.name.setFont(font)
|
|
||||||
self.name.setText(user)
|
|
||||||
|
|
||||||
self.time = QtGui.QLabel(self)
|
|
||||||
self.time.setGeometry(QtCore.QRect(width - 53, 7, 50, 20))
|
|
||||||
font.setPointSize(10)
|
|
||||||
font.setBold(False)
|
|
||||||
self.time.setFont(font)
|
|
||||||
self.time.setText(convert_time(time))
|
|
||||||
|
|
||||||
self.cancel = QtGui.QPushButton(self)
|
|
||||||
self.cancel.setGeometry(QtCore.QRect(width - 120, 2, 30, 30))
|
|
||||||
pixmap = QtGui.QPixmap(curr_directory() + '/images/decline.png')
|
|
||||||
icon = QtGui.QIcon(pixmap)
|
|
||||||
self.cancel.setIcon(icon)
|
|
||||||
self.cancel.setIconSize(QtCore.QSize(30, 30))
|
|
||||||
self.cancel.setVisible(state > 1)
|
|
||||||
self.cancel.clicked.connect(lambda: self.cancel_transfer(friend_number, file_number))
|
|
||||||
self.cancel.setStyleSheet('QPushButton:hover { border: 1px solid #3A3939; background-color: none;}')
|
|
||||||
|
|
||||||
self.accept_or_pause = QtGui.QPushButton(self)
|
|
||||||
self.accept_or_pause.setGeometry(QtCore.QRect(width - 170, 2, 30, 30))
|
|
||||||
if state == FILE_TRANSFER_MESSAGE_STATUS['INCOMING_NOT_STARTED']:
|
|
||||||
self.accept_or_pause.setVisible(True)
|
|
||||||
self.button_update('accept')
|
|
||||||
elif state in (0, 1, 5):
|
|
||||||
self.accept_or_pause.setVisible(False)
|
|
||||||
elif state == FILE_TRANSFER_MESSAGE_STATUS['PAUSED_BY_USER']: # setup for continue
|
|
||||||
self.accept_or_pause.setVisible(True)
|
|
||||||
self.button_update('resume')
|
|
||||||
else: # pause
|
|
||||||
self.accept_or_pause.setVisible(True)
|
|
||||||
self.button_update('pause')
|
|
||||||
self.accept_or_pause.clicked.connect(lambda: self.accept_or_pause_transfer(friend_number, file_number, size))
|
|
||||||
|
|
||||||
self.accept_or_pause.setStyleSheet('QPushButton:hover { border: 1px solid #3A3939; background-color: none}')
|
|
||||||
|
|
||||||
self.pb = QtGui.QProgressBar(self)
|
|
||||||
self.pb.setGeometry(QtCore.QRect(100, 7, 100, 20))
|
|
||||||
self.pb.setValue(0)
|
|
||||||
self.pb.setStyleSheet('QProgressBar { background-color: #302F2F; }')
|
|
||||||
if state < 2:
|
|
||||||
self.pb.setVisible(False)
|
|
||||||
|
|
||||||
self.file_name = DataLabel(self)
|
|
||||||
self.file_name.setGeometry(QtCore.QRect(210, 7, width - 400, 20))
|
|
||||||
font.setPointSize(12)
|
|
||||||
self.file_name.setFont(font)
|
|
||||||
file_size = size / 1024
|
|
||||||
if not file_size:
|
|
||||||
file_size = '{}B'.format(size)
|
|
||||||
elif file_size >= 1024:
|
|
||||||
file_size = '{}MB'.format(file_size / 1024)
|
|
||||||
else:
|
|
||||||
file_size = '{}KB'.format(file_size)
|
|
||||||
file_data = u'{} {}'.format(file_size, file_name)
|
|
||||||
self.file_name.setText(file_data)
|
|
||||||
self.saved_name = file_name
|
|
||||||
self.setFocusPolicy(QtCore.Qt.NoFocus)
|
|
||||||
self.paused = False
|
|
||||||
|
|
||||||
def cancel_transfer(self, friend_number, file_number):
|
|
||||||
pr = profile.Profile.get_instance()
|
|
||||||
pr.cancel_transfer(friend_number, file_number)
|
|
||||||
self.setStyleSheet('QListWidget { border: 1px solid #B40404; }')
|
|
||||||
self.cancel.setVisible(False)
|
|
||||||
self.accept_or_pause.setVisible(False)
|
|
||||||
self.pb.setVisible(False)
|
|
||||||
|
|
||||||
def accept_or_pause_transfer(self, friend_number, file_number, size):
|
|
||||||
if self.state == FILE_TRANSFER_MESSAGE_STATUS['INCOMING_NOT_STARTED']:
|
|
||||||
directory = QtGui.QFileDialog.getExistingDirectory(self,
|
|
||||||
QtGui.QApplication.translate("MainWindow", 'Choose folder', None, QtGui.QApplication.UnicodeUTF8),
|
|
||||||
curr_directory(),
|
|
||||||
QtGui.QFileDialog.ShowDirsOnly)
|
|
||||||
if directory:
|
|
||||||
pr = profile.Profile.get_instance()
|
|
||||||
pr.accept_transfer(self, directory + '/' + self.saved_name, friend_number, file_number, size)
|
|
||||||
self.button_update('pause')
|
|
||||||
elif self.state == FILE_TRANSFER_MESSAGE_STATUS['PAUSED_BY_USER']: # resume
|
|
||||||
self.paused = False
|
|
||||||
profile.Profile.get_instance().resume_transfer(friend_number, file_number)
|
|
||||||
self.button_update('pause')
|
|
||||||
self.state = FILE_TRANSFER_MESSAGE_STATUS['OUTGOING']
|
|
||||||
else: # pause
|
|
||||||
self.paused = True
|
|
||||||
self.state = FILE_TRANSFER_MESSAGE_STATUS['PAUSED_BY_USER']
|
|
||||||
profile.Profile.get_instance().pause_transfer(friend_number, file_number)
|
|
||||||
self.button_update('resume')
|
|
||||||
self.accept_or_pause.clearFocus()
|
|
||||||
|
|
||||||
def button_update(self, path):
|
|
||||||
pixmap = QtGui.QPixmap(curr_directory() + '/images/{}.png'.format(path))
|
|
||||||
icon = QtGui.QIcon(pixmap)
|
|
||||||
self.accept_or_pause.setIcon(icon)
|
|
||||||
self.accept_or_pause.setIconSize(QtCore.QSize(30, 30))
|
|
||||||
|
|
||||||
def convert(self, state):
|
|
||||||
# convert TOX_FILE_TRANSFER_STATE to FILE_TRANSFER_MESSAGE_STATUS
|
|
||||||
d = {0: 2, 1: 6, 2: 1, 3: 0, 4: 5}
|
|
||||||
return d[state]
|
|
||||||
|
|
||||||
@QtCore.Slot(int, float)
|
|
||||||
def update(self, state, progress):
|
|
||||||
self.pb.setValue(int(progress * 100))
|
|
||||||
state = self.convert(state)
|
|
||||||
if self.state != state:
|
|
||||||
if state == FILE_TRANSFER_MESSAGE_STATUS['CANCELLED']:
|
|
||||||
self.setStyleSheet('QListWidget { border: 1px solid #B40404; }')
|
|
||||||
self.cancel.setVisible(False)
|
|
||||||
self.accept_or_pause.setVisible(False)
|
|
||||||
self.pb.setVisible(False)
|
|
||||||
self.state = state
|
|
||||||
elif state == FILE_TRANSFER_MESSAGE_STATUS['FINISHED']:
|
|
||||||
self.accept_or_pause.setVisible(False)
|
|
||||||
self.pb.setVisible(False)
|
|
||||||
self.cancel.setVisible(False)
|
|
||||||
self.setStyleSheet('QListWidget { border: 1px solid green; }')
|
|
||||||
self.state = state
|
|
||||||
elif state == FILE_TRANSFER_MESSAGE_STATUS['PAUSED_BY_FRIEND']:
|
|
||||||
self.accept_or_pause.setVisible(False)
|
|
||||||
self.setStyleSheet('QListWidget { border: 1px solid #FF8000; }')
|
|
||||||
self.state = state
|
|
||||||
elif state == FILE_TRANSFER_MESSAGE_STATUS['PAUSED_BY_USER']:
|
|
||||||
self.button_update('resume') # setup button continue
|
|
||||||
self.setStyleSheet('QListWidget { border: 1px solid green; }')
|
|
||||||
self.state = state
|
|
||||||
elif not self.paused: # active
|
|
||||||
self.accept_or_pause.setVisible(True) # setup to pause
|
|
||||||
self.button_update('pause')
|
|
||||||
self.setStyleSheet('QListWidget { border: 1px solid green; }')
|
|
||||||
self.state = state
|
|
||||||
|
|
||||||
|
|
||||||
class InlineImageItem(QtGui.QWidget):
|
|
||||||
|
|
||||||
def __init__(self, data, width, parent=None):
|
|
||||||
|
|
||||||
QtGui.QWidget.__init__(self, parent)
|
|
||||||
self.resize(QtCore.QSize(width, 500))
|
|
||||||
self._image_label = QtGui.QLabel(self)
|
|
||||||
self._image_label.raise_()
|
|
||||||
self._image_label.setAutoFillBackground(True)
|
|
||||||
self._image_label.setScaledContents(False)
|
|
||||||
self.pixmap = QtGui.QPixmap()
|
|
||||||
self.pixmap.loadFromData(QtCore.QByteArray(data), "PNG")
|
|
||||||
max_size = width - 40
|
|
||||||
if self.pixmap.width() <= max_size:
|
|
||||||
self._image_label.setPixmap(self.pixmap)
|
|
||||||
self.resize(QtCore.QSize(max_size, self.pixmap.height()))
|
|
||||||
else:
|
|
||||||
pixmap = self.pixmap.scaled(max_size, max_size, QtCore.Qt.KeepAspectRatio)
|
|
||||||
self._image_label.setPixmap(pixmap)
|
|
||||||
self.resize(QtCore.QSize(max_size, pixmap.height()))
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -1,107 +0,0 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
|
|
||||||
try:
|
|
||||||
from PySide import QtCore, QtGui
|
|
||||||
except ImportError:
|
|
||||||
from PyQt4 import QtCore, QtGui
|
|
||||||
from widgets import *
|
|
||||||
|
|
||||||
|
|
||||||
class NickEdit(QtGui.QPlainTextEdit):
|
|
||||||
|
|
||||||
def __init__(self, parent):
|
|
||||||
super(NickEdit, self).__init__(parent)
|
|
||||||
self.parent = parent
|
|
||||||
|
|
||||||
def keyPressEvent(self, event):
|
|
||||||
if event.key() == QtCore.Qt.Key_Return:
|
|
||||||
self.parent.create_profile()
|
|
||||||
else:
|
|
||||||
super(NickEdit, self).keyPressEvent(event)
|
|
||||||
|
|
||||||
|
|
||||||
class LoginScreen(CenteredWidget):
|
|
||||||
|
|
||||||
def __init__(self):
|
|
||||||
super(LoginScreen, self).__init__()
|
|
||||||
self.initUI()
|
|
||||||
self.center()
|
|
||||||
|
|
||||||
def initUI(self):
|
|
||||||
self.resize(400, 200)
|
|
||||||
self.setMinimumSize(QtCore.QSize(400, 200))
|
|
||||||
self.setMaximumSize(QtCore.QSize(400, 200))
|
|
||||||
self.new_profile = QtGui.QPushButton(self)
|
|
||||||
self.new_profile.setGeometry(QtCore.QRect(20, 150, 171, 27))
|
|
||||||
self.new_profile.clicked.connect(self.create_profile)
|
|
||||||
self.label = QtGui.QLabel(self)
|
|
||||||
self.label.setGeometry(QtCore.QRect(20, 70, 101, 17))
|
|
||||||
self.new_name = NickEdit(self)
|
|
||||||
self.new_name.setGeometry(QtCore.QRect(20, 100, 171, 31))
|
|
||||||
self.load_profile = QtGui.QPushButton(self)
|
|
||||||
self.load_profile.setGeometry(QtCore.QRect(220, 150, 161, 27))
|
|
||||||
self.load_profile.clicked.connect(self.load_ex_profile)
|
|
||||||
self.default = QtGui.QCheckBox(self)
|
|
||||||
self.default.setGeometry(QtCore.QRect(220, 110, 131, 22))
|
|
||||||
self.groupBox = QtGui.QGroupBox(self)
|
|
||||||
self.groupBox.setGeometry(QtCore.QRect(210, 40, 181, 151))
|
|
||||||
self.comboBox = QtGui.QComboBox(self.groupBox)
|
|
||||||
self.comboBox.setGeometry(QtCore.QRect(10, 30, 161, 27))
|
|
||||||
self.groupBox_2 = QtGui.QGroupBox(self)
|
|
||||||
self.groupBox_2.setGeometry(QtCore.QRect(10, 40, 191, 151))
|
|
||||||
self.toxygen = QtGui.QLabel(self)
|
|
||||||
self.groupBox.raise_()
|
|
||||||
self.groupBox_2.raise_()
|
|
||||||
self.comboBox.raise_()
|
|
||||||
self.default.raise_()
|
|
||||||
self.load_profile.raise_()
|
|
||||||
self.new_name.raise_()
|
|
||||||
self.new_profile.raise_()
|
|
||||||
self.toxygen.setGeometry(QtCore.QRect(160, 10, 90, 21))
|
|
||||||
font = QtGui.QFont()
|
|
||||||
font.setFamily("Impact")
|
|
||||||
font.setPointSize(16)
|
|
||||||
self.toxygen.setFont(font)
|
|
||||||
self.toxygen.setObjectName("toxygen")
|
|
||||||
self.type = 0
|
|
||||||
self.number = -1
|
|
||||||
self.load_as_default = False
|
|
||||||
self.name = None
|
|
||||||
self.retranslateUi()
|
|
||||||
QtCore.QMetaObject.connectSlotsByName(self)
|
|
||||||
|
|
||||||
def retranslateUi(self):
|
|
||||||
self.setWindowTitle(QtGui.QApplication.translate("login", "Log in", None, QtGui.QApplication.UnicodeUTF8))
|
|
||||||
self.new_profile.setText(QtGui.QApplication.translate("login", "Create", None, QtGui.QApplication.UnicodeUTF8))
|
|
||||||
self.label.setText(QtGui.QApplication.translate("login", "Profile name:", None, QtGui.QApplication.UnicodeUTF8))
|
|
||||||
self.load_profile.setText(QtGui.QApplication.translate("login", "Load profile", None, QtGui.QApplication.UnicodeUTF8))
|
|
||||||
self.default.setText(QtGui.QApplication.translate("login", "Use as default", None, QtGui.QApplication.UnicodeUTF8))
|
|
||||||
self.groupBox.setTitle(QtGui.QApplication.translate("login", "Load existing profile", None, QtGui.QApplication.UnicodeUTF8))
|
|
||||||
self.groupBox_2.setTitle(QtGui.QApplication.translate("login", "Create new profile", None, QtGui.QApplication.UnicodeUTF8))
|
|
||||||
self.toxygen.setText(QtGui.QApplication.translate("login", "toxygen", None, QtGui.QApplication.UnicodeUTF8))
|
|
||||||
|
|
||||||
def create_profile(self):
|
|
||||||
self.type = 1
|
|
||||||
self.name = self.new_name.toPlainText()
|
|
||||||
self.close()
|
|
||||||
|
|
||||||
def load_ex_profile(self):
|
|
||||||
if not self.create_only:
|
|
||||||
self.type = 2
|
|
||||||
self.number = self.comboBox.currentIndex()
|
|
||||||
self.load_as_default = self.default.isChecked()
|
|
||||||
self.close()
|
|
||||||
|
|
||||||
def update_select(self, data):
|
|
||||||
list_of_profiles = []
|
|
||||||
for elem in data:
|
|
||||||
list_of_profiles.append(elem)
|
|
||||||
self.comboBox.addItems(list_of_profiles)
|
|
||||||
self.create_only = not list_of_profiles
|
|
||||||
|
|
||||||
def update_on_close(self, func):
|
|
||||||
self.onclose = func
|
|
||||||
|
|
||||||
def closeEvent(self, event):
|
|
||||||
self.onclose(self.type, self.number, self.load_as_default, self.name)
|
|
||||||
event.accept()
|
|
341
src/main.py
@ -1,341 +0,0 @@
|
|||||||
import sys
|
|
||||||
from loginscreen import LoginScreen
|
|
||||||
from settings import *
|
|
||||||
try:
|
|
||||||
from PySide import QtCore, QtGui
|
|
||||||
except ImportError:
|
|
||||||
from PyQt4 import QtCore, QtGui
|
|
||||||
from bootstrap import node_generator
|
|
||||||
from mainscreen import MainWindow
|
|
||||||
from profile import tox_factory
|
|
||||||
from callbacks import init_callbacks
|
|
||||||
from util import curr_directory, get_style
|
|
||||||
import styles.style
|
|
||||||
import locale
|
|
||||||
import toxencryptsave
|
|
||||||
from passwordscreen import PasswordScreen
|
|
||||||
import profile
|
|
||||||
from plugin_support import PluginLoader
|
|
||||||
|
|
||||||
|
|
||||||
class Toxygen(object):
|
|
||||||
|
|
||||||
def __init__(self, path=None):
|
|
||||||
super(Toxygen, self).__init__()
|
|
||||||
self.tox = self.ms = self.init = self.mainloop = self.avloop = None
|
|
||||||
self.path = path
|
|
||||||
|
|
||||||
def enter_pass(self, data):
|
|
||||||
"""
|
|
||||||
Show password screen
|
|
||||||
"""
|
|
||||||
tmp = [data]
|
|
||||||
p = PasswordScreen(toxencryptsave.LibToxEncryptSave.get_instance(), tmp)
|
|
||||||
p.show()
|
|
||||||
self.app.connect(self.app, QtCore.SIGNAL("lastWindowClosed()"), self.app, QtCore.SLOT("quit()"))
|
|
||||||
self.app.exec_()
|
|
||||||
if tmp[0] == data:
|
|
||||||
raise SystemExit()
|
|
||||||
else:
|
|
||||||
return tmp[0]
|
|
||||||
|
|
||||||
def main(self):
|
|
||||||
"""
|
|
||||||
Main function of app. loads login screen if needed and starts main screen
|
|
||||||
"""
|
|
||||||
app = QtGui.QApplication(sys.argv)
|
|
||||||
app.setWindowIcon(QtGui.QIcon(curr_directory() + '/images/icon.png'))
|
|
||||||
self.app = app
|
|
||||||
|
|
||||||
# application color scheme
|
|
||||||
with open(curr_directory() + '/styles/style.qss') as fl:
|
|
||||||
dark_style = fl.read()
|
|
||||||
app.setStyleSheet(dark_style)
|
|
||||||
|
|
||||||
encrypt_save = toxencryptsave.LibToxEncryptSave()
|
|
||||||
|
|
||||||
if self.path is not None:
|
|
||||||
path = os.path.dirname(self.path.encode(locale.getpreferredencoding())) + '/'
|
|
||||||
name = os.path.basename(self.path.encode(locale.getpreferredencoding()))[:-4]
|
|
||||||
data = ProfileHelper(path, name).open_profile()
|
|
||||||
if encrypt_save.is_data_encrypted(data):
|
|
||||||
data = self.enter_pass(data)
|
|
||||||
settings = Settings(name)
|
|
||||||
self.tox = tox_factory(data, settings)
|
|
||||||
else:
|
|
||||||
auto_profile = Settings.get_auto_profile()
|
|
||||||
if not auto_profile:
|
|
||||||
# show login screen if default profile not found
|
|
||||||
current_locale = QtCore.QLocale()
|
|
||||||
curr_lang = current_locale.languageToString(current_locale.language())
|
|
||||||
langs = Settings.supported_languages()
|
|
||||||
if curr_lang in map(lambda x: x[0], langs):
|
|
||||||
lang_path = filter(lambda x: x[0] == curr_lang, langs)[0][1]
|
|
||||||
translator = QtCore.QTranslator()
|
|
||||||
translator.load(curr_directory() + '/translations/' + lang_path)
|
|
||||||
app.installTranslator(translator)
|
|
||||||
app.translator = translator
|
|
||||||
ls = LoginScreen()
|
|
||||||
ls.setWindowIconText("Toxygen")
|
|
||||||
profiles = ProfileHelper.find_profiles()
|
|
||||||
ls.update_select(map(lambda x: x[1], profiles))
|
|
||||||
_login = self.Login(profiles)
|
|
||||||
ls.update_on_close(_login.login_screen_close)
|
|
||||||
ls.show()
|
|
||||||
app.connect(app, QtCore.SIGNAL("lastWindowClosed()"), app, QtCore.SLOT("quit()"))
|
|
||||||
app.exec_()
|
|
||||||
if not _login.t:
|
|
||||||
return
|
|
||||||
elif _login.t == 1: # create new profile
|
|
||||||
name = _login.name if _login.name else 'toxygen_user'
|
|
||||||
self.tox = tox_factory()
|
|
||||||
self.tox.self_set_name(_login.name if _login.name else 'Toxygen User')
|
|
||||||
self.tox.self_set_status_message('Toxing on Toxygen')
|
|
||||||
ProfileHelper(Settings.get_default_path(), name).save_profile(self.tox.get_savedata())
|
|
||||||
path = Settings.get_default_path()
|
|
||||||
settings = Settings(name)
|
|
||||||
else: # load existing profile
|
|
||||||
path, name = _login.get_data()
|
|
||||||
if _login.default:
|
|
||||||
Settings.set_auto_profile(path, name)
|
|
||||||
data = ProfileHelper(path, name).open_profile()
|
|
||||||
if encrypt_save.is_data_encrypted(data):
|
|
||||||
data = self.enter_pass(data)
|
|
||||||
settings = Settings(name)
|
|
||||||
self.tox = tox_factory(data, settings)
|
|
||||||
else:
|
|
||||||
path, name = auto_profile
|
|
||||||
path = path.encode(locale.getpreferredencoding())
|
|
||||||
data = ProfileHelper(path, name).open_profile()
|
|
||||||
if encrypt_save.is_data_encrypted(data):
|
|
||||||
data = self.enter_pass(data)
|
|
||||||
settings = Settings(name)
|
|
||||||
self.tox = tox_factory(data, settings)
|
|
||||||
|
|
||||||
if ProfileHelper.is_active_profile(path, name): # profile is in use
|
|
||||||
reply = QtGui.QMessageBox.question(None,
|
|
||||||
'Profile {}'.format(name),
|
|
||||||
QtGui.QApplication.translate("login", 'Looks like other instance of Toxygen uses this profile! Continue?', None, QtGui.QApplication.UnicodeUTF8),
|
|
||||||
QtGui.QMessageBox.Yes,
|
|
||||||
QtGui.QMessageBox.No)
|
|
||||||
if reply != QtGui.QMessageBox.Yes:
|
|
||||||
return
|
|
||||||
else:
|
|
||||||
settings.set_active_profile()
|
|
||||||
|
|
||||||
lang = filter(lambda x: x[0] == settings['language'], Settings.supported_languages())[0]
|
|
||||||
translator = QtCore.QTranslator()
|
|
||||||
translator.load(curr_directory() + '/translations/' + lang[1])
|
|
||||||
app.installTranslator(translator)
|
|
||||||
app.translator = translator
|
|
||||||
|
|
||||||
self.ms = MainWindow(self.tox, self.reset)
|
|
||||||
|
|
||||||
# tray icon
|
|
||||||
self.tray = QtGui.QSystemTrayIcon(QtGui.QIcon(curr_directory() + '/images/icon.png'))
|
|
||||||
self.tray.setObjectName('tray')
|
|
||||||
|
|
||||||
class Menu(QtGui.QMenu):
|
|
||||||
|
|
||||||
def newStatus(self, status):
|
|
||||||
profile.Profile.get_instance().set_status(status)
|
|
||||||
self.aboutToShow()
|
|
||||||
self.hide()
|
|
||||||
|
|
||||||
def aboutToShow(self):
|
|
||||||
status = profile.Profile.get_instance().status
|
|
||||||
act = self.act
|
|
||||||
if status is None:
|
|
||||||
self.actions()[1].setVisible(False)
|
|
||||||
else:
|
|
||||||
self.actions()[1].setVisible(True)
|
|
||||||
act.actions()[0].setChecked(False)
|
|
||||||
act.actions()[1].setChecked(False)
|
|
||||||
act.actions()[2].setChecked(False)
|
|
||||||
act.actions()[status].setChecked(True)
|
|
||||||
|
|
||||||
def languageChange(self, *args, **kwargs):
|
|
||||||
self.actions()[0].setText(QtGui.QApplication.translate('tray', 'Open Toxygen', None, QtGui.QApplication.UnicodeUTF8))
|
|
||||||
self.actions()[1].setText(QtGui.QApplication.translate('tray', 'Set status', None, QtGui.QApplication.UnicodeUTF8))
|
|
||||||
self.actions()[2].setText(QtGui.QApplication.translate('tray', 'Exit', None, QtGui.QApplication.UnicodeUTF8))
|
|
||||||
self.act.actions()[0].setText(QtGui.QApplication.translate('tray', 'Online', None, QtGui.QApplication.UnicodeUTF8))
|
|
||||||
self.act.actions()[1].setText(QtGui.QApplication.translate('tray', 'Away', None, QtGui.QApplication.UnicodeUTF8))
|
|
||||||
self.act.actions()[2].setText(QtGui.QApplication.translate('tray', 'Busy', None, QtGui.QApplication.UnicodeUTF8))
|
|
||||||
|
|
||||||
m = Menu()
|
|
||||||
show = m.addAction(QtGui.QApplication.translate('tray', 'Open Toxygen', None, QtGui.QApplication.UnicodeUTF8))
|
|
||||||
sub = m.addMenu(QtGui.QApplication.translate('tray', 'Set status', None, QtGui.QApplication.UnicodeUTF8))
|
|
||||||
onl = sub.addAction(QtGui.QApplication.translate('tray', 'Online', None, QtGui.QApplication.UnicodeUTF8))
|
|
||||||
away = sub.addAction(QtGui.QApplication.translate('tray', 'Away', None, QtGui.QApplication.UnicodeUTF8))
|
|
||||||
busy = sub.addAction(QtGui.QApplication.translate('tray', 'Busy', None, QtGui.QApplication.UnicodeUTF8))
|
|
||||||
onl.setCheckable(True)
|
|
||||||
away.setCheckable(True)
|
|
||||||
busy.setCheckable(True)
|
|
||||||
m.act = sub
|
|
||||||
exit = m.addAction(QtGui.QApplication.translate('tray', 'Exit', None, QtGui.QApplication.UnicodeUTF8))
|
|
||||||
|
|
||||||
def show_window():
|
|
||||||
if not self.ms.isActiveWindow():
|
|
||||||
self.ms.setWindowState(self.ms.windowState() & ~QtCore.Qt.WindowMinimized | QtCore.Qt.WindowActive)
|
|
||||||
self.ms.activateWindow()
|
|
||||||
self.ms.show()
|
|
||||||
|
|
||||||
m.connect(show, QtCore.SIGNAL("triggered()"), show_window)
|
|
||||||
m.connect(exit, QtCore.SIGNAL("triggered()"), lambda: app.exit())
|
|
||||||
m.connect(m, QtCore.SIGNAL("aboutToShow()"), lambda: m.aboutToShow())
|
|
||||||
sub.connect(onl, QtCore.SIGNAL("triggered()"), lambda: m.newStatus(0))
|
|
||||||
sub.connect(away, QtCore.SIGNAL("triggered()"), lambda: m.newStatus(1))
|
|
||||||
sub.connect(busy, QtCore.SIGNAL("triggered()"), lambda: m.newStatus(2))
|
|
||||||
self.tray.setContextMenu(m)
|
|
||||||
self.tray.show()
|
|
||||||
|
|
||||||
self.ms.show()
|
|
||||||
QtGui.QApplication.setStyle(get_style(settings['theme'])) # set application style
|
|
||||||
|
|
||||||
plugin_helper = PluginLoader(self.tox, settings) # plugin support
|
|
||||||
plugin_helper.load()
|
|
||||||
|
|
||||||
# init thread
|
|
||||||
self.init = self.InitThread(self.tox, self.ms, self.tray)
|
|
||||||
self.init.start()
|
|
||||||
|
|
||||||
# starting threads for tox iterate and toxav iterate
|
|
||||||
self.mainloop = self.ToxIterateThread(self.tox)
|
|
||||||
self.mainloop.start()
|
|
||||||
self.avloop = self.ToxAVIterateThread(self.tox.AV)
|
|
||||||
self.avloop.start()
|
|
||||||
|
|
||||||
app.connect(app, QtCore.SIGNAL("lastWindowClosed()"), app, QtCore.SLOT("quit()"))
|
|
||||||
app.exec_()
|
|
||||||
self.init.stop = True
|
|
||||||
self.mainloop.stop = True
|
|
||||||
self.avloop.stop = True
|
|
||||||
plugin_helper.stop()
|
|
||||||
self.mainloop.wait()
|
|
||||||
self.init.wait()
|
|
||||||
self.avloop.wait()
|
|
||||||
data = self.tox.get_savedata()
|
|
||||||
ProfileHelper.get_instance().save_profile(data)
|
|
||||||
settings.close()
|
|
||||||
del self.tox
|
|
||||||
|
|
||||||
def reset(self):
|
|
||||||
"""
|
|
||||||
Create new tox instance (new network settings)
|
|
||||||
:return: tox instance
|
|
||||||
"""
|
|
||||||
self.mainloop.stop = True
|
|
||||||
self.init.stop = True
|
|
||||||
self.avloop.stop = True
|
|
||||||
self.mainloop.wait()
|
|
||||||
self.init.wait()
|
|
||||||
self.avloop.wait()
|
|
||||||
data = self.tox.get_savedata()
|
|
||||||
ProfileHelper.get_instance().save_profile(data)
|
|
||||||
del self.tox
|
|
||||||
# create new tox instance
|
|
||||||
self.tox = tox_factory(data, Settings.get_instance())
|
|
||||||
# init thread
|
|
||||||
self.init = self.InitThread(self.tox, self.ms, self.tray)
|
|
||||||
self.init.start()
|
|
||||||
|
|
||||||
# starting threads for tox iterate and toxav iterate
|
|
||||||
self.mainloop = self.ToxIterateThread(self.tox)
|
|
||||||
self.mainloop.start()
|
|
||||||
|
|
||||||
self.avloop = self.ToxAVIterateThread(self.tox.AV)
|
|
||||||
self.avloop.start()
|
|
||||||
|
|
||||||
plugin_helper = PluginLoader.get_instance()
|
|
||||||
plugin_helper.set_tox(self.tox)
|
|
||||||
|
|
||||||
return self.tox
|
|
||||||
|
|
||||||
# -----------------------------------------------------------------------------------------------------------------
|
|
||||||
# Inner classes
|
|
||||||
# -----------------------------------------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
class InitThread(QtCore.QThread):
|
|
||||||
|
|
||||||
def __init__(self, tox, ms, tray):
|
|
||||||
QtCore.QThread.__init__(self)
|
|
||||||
self.tox, self.ms, self.tray = tox, ms, tray
|
|
||||||
self.stop = False
|
|
||||||
|
|
||||||
def run(self):
|
|
||||||
# initializing callbacks
|
|
||||||
init_callbacks(self.tox, self.ms, self.tray)
|
|
||||||
# bootstrap
|
|
||||||
try:
|
|
||||||
for data in node_generator():
|
|
||||||
if self.stop:
|
|
||||||
return
|
|
||||||
self.tox.bootstrap(*data)
|
|
||||||
except:
|
|
||||||
pass
|
|
||||||
for _ in xrange(10):
|
|
||||||
if self.stop:
|
|
||||||
return
|
|
||||||
self.msleep(1000)
|
|
||||||
while not self.tox.self_get_connection_status():
|
|
||||||
try:
|
|
||||||
for data in node_generator():
|
|
||||||
if self.stop:
|
|
||||||
return
|
|
||||||
self.tox.bootstrap(*data)
|
|
||||||
except:
|
|
||||||
pass
|
|
||||||
finally:
|
|
||||||
self.msleep(5000)
|
|
||||||
|
|
||||||
class ToxIterateThread(QtCore.QThread):
|
|
||||||
|
|
||||||
def __init__(self, tox):
|
|
||||||
QtCore.QThread.__init__(self)
|
|
||||||
self.tox = tox
|
|
||||||
self.stop = False
|
|
||||||
|
|
||||||
def run(self):
|
|
||||||
while not self.stop:
|
|
||||||
self.tox.iterate()
|
|
||||||
self.msleep(self.tox.iteration_interval())
|
|
||||||
|
|
||||||
class ToxAVIterateThread(QtCore.QThread):
|
|
||||||
|
|
||||||
def __init__(self, toxav):
|
|
||||||
QtCore.QThread.__init__(self)
|
|
||||||
self.toxav = toxav
|
|
||||||
self.stop = False
|
|
||||||
|
|
||||||
def run(self):
|
|
||||||
while not self.stop:
|
|
||||||
self.toxav.iterate()
|
|
||||||
self.msleep(self.toxav.iteration_interval())
|
|
||||||
|
|
||||||
class Login(object):
|
|
||||||
|
|
||||||
def __init__(self, arr):
|
|
||||||
self.arr = arr
|
|
||||||
|
|
||||||
def login_screen_close(self, t, number=-1, default=False, name=None):
|
|
||||||
""" Function which processes data from login screen
|
|
||||||
:param t: 0 - window was closed, 1 - new profile was created, 2 - profile loaded
|
|
||||||
:param number: num of chosen profile in list (-1 by default)
|
|
||||||
:param default: was or not chosen profile marked as default
|
|
||||||
:param name: name of new profile
|
|
||||||
"""
|
|
||||||
self.t = t
|
|
||||||
self.num = number
|
|
||||||
self.default = default
|
|
||||||
self.name = name
|
|
||||||
|
|
||||||
def get_data(self):
|
|
||||||
return self.arr[self.num]
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
if len(sys.argv) == 1:
|
|
||||||
toxygen = Toxygen()
|
|
||||||
else: # path to profile
|
|
||||||
toxygen = Toxygen(sys.argv[1])
|
|
||||||
toxygen.main()
|
|
@ -1,573 +0,0 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
|
|
||||||
from menu import *
|
|
||||||
from profile import *
|
|
||||||
from list_items import *
|
|
||||||
from widgets import QRightClickButton
|
|
||||||
import plugin_support
|
|
||||||
|
|
||||||
|
|
||||||
class MessageArea(QtGui.QPlainTextEdit):
|
|
||||||
|
|
||||||
def __init__(self, parent, form):
|
|
||||||
super(MessageArea, self).__init__(parent)
|
|
||||||
self.parent = form
|
|
||||||
self.timer = QtCore.QTimer(self)
|
|
||||||
self.timer.timeout.connect(lambda: self.parent.profile.send_typing(False))
|
|
||||||
|
|
||||||
def keyPressEvent(self, event):
|
|
||||||
if event.key() == QtCore.Qt.Key_Return:
|
|
||||||
modifiers = event.modifiers()
|
|
||||||
if modifiers & QtCore.Qt.ControlModifier or modifiers & QtCore.Qt.ShiftModifier:
|
|
||||||
self.appendPlainText('')
|
|
||||||
else:
|
|
||||||
if self.timer.isActive():
|
|
||||||
self.timer.stop()
|
|
||||||
self.parent.profile.send_typing(False)
|
|
||||||
self.parent.send_message()
|
|
||||||
elif event.key() == QtCore.Qt.Key_Up and not self.toPlainText():
|
|
||||||
self.appendPlainText(Profile.get_instance().get_last_message())
|
|
||||||
else:
|
|
||||||
self.parent.profile.send_typing(True)
|
|
||||||
if self.timer.isActive():
|
|
||||||
self.timer.stop()
|
|
||||||
self.timer.start(5000)
|
|
||||||
super(MessageArea, self).keyPressEvent(event)
|
|
||||||
|
|
||||||
|
|
||||||
class MainWindow(QtGui.QMainWindow):
|
|
||||||
|
|
||||||
def __init__(self, tox, reset):
|
|
||||||
super(MainWindow, self).__init__()
|
|
||||||
self.reset = reset
|
|
||||||
self.initUI(tox)
|
|
||||||
|
|
||||||
def setup_menu(self, MainWindow):
|
|
||||||
self.menubar = QtGui.QMenuBar(MainWindow)
|
|
||||||
self.menubar.setObjectName("menubar")
|
|
||||||
self.menubar.setNativeMenuBar(False)
|
|
||||||
self.menubar.setMinimumSize(self.width(), 25)
|
|
||||||
self.menubar.setMaximumSize(self.width(), 25)
|
|
||||||
self.menubar.setBaseSize(self.width(), 25)
|
|
||||||
|
|
||||||
self.menuProfile = QtGui.QMenu(self.menubar)
|
|
||||||
self.menuProfile.setObjectName("menuProfile")
|
|
||||||
self.menuSettings = QtGui.QMenu(self.menubar)
|
|
||||||
self.menuSettings.setObjectName("menuSettings")
|
|
||||||
self.menuPlugins = QtGui.QMenu(self.menubar)
|
|
||||||
self.menuPlugins.setObjectName("menuPlugins")
|
|
||||||
self.menuAbout = QtGui.QMenu(self.menubar)
|
|
||||||
self.menuAbout.setObjectName("menuAbout")
|
|
||||||
|
|
||||||
self.actionAdd_friend = QtGui.QAction(MainWindow)
|
|
||||||
self.actionAdd_friend.setObjectName("actionAdd_friend")
|
|
||||||
self.actionProfile_settings = QtGui.QAction(MainWindow)
|
|
||||||
self.actionProfile_settings.setObjectName("actionProfile_settings")
|
|
||||||
self.actionPrivacy_settings = QtGui.QAction(MainWindow)
|
|
||||||
self.actionPrivacy_settings.setObjectName("actionPrivacy_settings")
|
|
||||||
self.actionInterface_settings = QtGui.QAction(MainWindow)
|
|
||||||
self.actionInterface_settings.setObjectName("actionInterface_settings")
|
|
||||||
self.actionNotifications = QtGui.QAction(MainWindow)
|
|
||||||
self.actionNotifications.setObjectName("actionNotifications")
|
|
||||||
self.actionNetwork = QtGui.QAction(MainWindow)
|
|
||||||
self.actionNetwork.setObjectName("actionNetwork")
|
|
||||||
self.actionAbout_program = QtGui.QAction(MainWindow)
|
|
||||||
self.actionAbout_program.setObjectName("actionAbout_program")
|
|
||||||
self.actionSettings = QtGui.QAction(MainWindow)
|
|
||||||
self.actionSettings.setObjectName("actionSettings")
|
|
||||||
self.audioSettings = QtGui.QAction(MainWindow)
|
|
||||||
self.pluginData = QtGui.QAction(MainWindow)
|
|
||||||
self.menuProfile.addAction(self.actionAdd_friend)
|
|
||||||
self.menuProfile.addAction(self.actionSettings)
|
|
||||||
self.menuSettings.addAction(self.actionPrivacy_settings)
|
|
||||||
self.menuSettings.addAction(self.actionInterface_settings)
|
|
||||||
self.menuSettings.addAction(self.actionNotifications)
|
|
||||||
self.menuSettings.addAction(self.actionNetwork)
|
|
||||||
self.menuSettings.addAction(self.audioSettings)
|
|
||||||
self.menuPlugins.addAction(self.pluginData)
|
|
||||||
self.menuAbout.addAction(self.actionAbout_program)
|
|
||||||
self.menubar.addAction(self.menuProfile.menuAction())
|
|
||||||
self.menubar.addAction(self.menuSettings.menuAction())
|
|
||||||
self.menubar.addAction(self.menuPlugins.menuAction())
|
|
||||||
self.menubar.addAction(self.menuAbout.menuAction())
|
|
||||||
|
|
||||||
self.actionAbout_program.triggered.connect(self.about_program)
|
|
||||||
self.actionNetwork.triggered.connect(self.network_settings)
|
|
||||||
self.actionAdd_friend.triggered.connect(self.add_contact)
|
|
||||||
self.actionSettings.triggered.connect(self.profile_settings)
|
|
||||||
self.actionPrivacy_settings.triggered.connect(self.privacy_settings)
|
|
||||||
self.actionInterface_settings.triggered.connect(self.interface_settings)
|
|
||||||
self.actionNotifications.triggered.connect(self.notification_settings)
|
|
||||||
self.audioSettings.triggered.connect(self.audio_settings)
|
|
||||||
self.pluginData.triggered.connect(self.plugins_menu)
|
|
||||||
QtCore.QMetaObject.connectSlotsByName(MainWindow)
|
|
||||||
|
|
||||||
def languageChange(self, *args, **kwargs):
|
|
||||||
self.retranslateUi()
|
|
||||||
|
|
||||||
def retranslateUi(self):
|
|
||||||
self.menuPlugins.setTitle(QtGui.QApplication.translate("MainWindow", "Plugins", None, QtGui.QApplication.UnicodeUTF8))
|
|
||||||
self.pluginData.setText(QtGui.QApplication.translate("MainWindow", "List of plugins", None, QtGui.QApplication.UnicodeUTF8))
|
|
||||||
self.menuProfile.setTitle(QtGui.QApplication.translate("MainWindow", "Profile", None, QtGui.QApplication.UnicodeUTF8))
|
|
||||||
self.menuSettings.setTitle(QtGui.QApplication.translate("MainWindow", "Settings", None, QtGui.QApplication.UnicodeUTF8))
|
|
||||||
self.menuAbout.setTitle(QtGui.QApplication.translate("MainWindow", "About", None, QtGui.QApplication.UnicodeUTF8))
|
|
||||||
self.actionAdd_friend.setText(QtGui.QApplication.translate("MainWindow", "Add contact", None, QtGui.QApplication.UnicodeUTF8))
|
|
||||||
self.actionProfile_settings.setText(QtGui.QApplication.translate("MainWindow", "Profile", None, QtGui.QApplication.UnicodeUTF8))
|
|
||||||
self.actionPrivacy_settings.setText(QtGui.QApplication.translate("MainWindow", "Privacy", None, QtGui.QApplication.UnicodeUTF8))
|
|
||||||
self.actionInterface_settings.setText(QtGui.QApplication.translate("MainWindow", "Interface", None, QtGui.QApplication.UnicodeUTF8))
|
|
||||||
self.actionNotifications.setText(QtGui.QApplication.translate("MainWindow", "Notifications", None, QtGui.QApplication.UnicodeUTF8))
|
|
||||||
self.actionNetwork.setText(QtGui.QApplication.translate("MainWindow", "Network", None, QtGui.QApplication.UnicodeUTF8))
|
|
||||||
self.actionAbout_program.setText(QtGui.QApplication.translate("MainWindow", "About program", None, QtGui.QApplication.UnicodeUTF8))
|
|
||||||
self.actionSettings.setText(QtGui.QApplication.translate("MainWindow", "Settings", None, QtGui.QApplication.UnicodeUTF8))
|
|
||||||
self.audioSettings.setText(QtGui.QApplication.translate("MainWindow", "Audio", None, QtGui.QApplication.UnicodeUTF8))
|
|
||||||
self.contact_name.setPlaceholderText(QtGui.QApplication.translate("MainWindow", "Search", None, QtGui.QApplication.UnicodeUTF8))
|
|
||||||
self.screenshotButton.setToolTip(QtGui.QApplication.translate("MainWindow", "Send screenshot", None, QtGui.QApplication.UnicodeUTF8))
|
|
||||||
self.fileTransferButton.setToolTip(QtGui.QApplication.translate("MainWindow", "Send file", None, QtGui.QApplication.UnicodeUTF8))
|
|
||||||
self.sendMessageButton.setToolTip(QtGui.QApplication.translate("MainWindow", "Send message", None, QtGui.QApplication.UnicodeUTF8))
|
|
||||||
self.callButton.setToolTip(QtGui.QApplication.translate("MainWindow", "Start audio call with friend", None, QtGui.QApplication.UnicodeUTF8))
|
|
||||||
self.online_contacts.clear()
|
|
||||||
self.online_contacts.addItem(QtGui.QApplication.translate("MainWindow", "All", None, QtGui.QApplication.UnicodeUTF8))
|
|
||||||
self.online_contacts.addItem(QtGui.QApplication.translate("MainWindow", "Online", None, QtGui.QApplication.UnicodeUTF8))
|
|
||||||
self.online_contacts.setCurrentIndex(int(Settings.get_instance()['show_online_friends']))
|
|
||||||
|
|
||||||
def setup_right_bottom(self, Form):
|
|
||||||
Form.setObjectName("right_bottom")
|
|
||||||
Form.resize(650, 60)
|
|
||||||
self.messageEdit = MessageArea(Form, self)
|
|
||||||
self.messageEdit.setGeometry(QtCore.QRect(0, 3, 450, 55))
|
|
||||||
self.messageEdit.setObjectName("messageEdit")
|
|
||||||
font = QtGui.QFont()
|
|
||||||
font.setPointSize(10)
|
|
||||||
self.messageEdit.setFont(font)
|
|
||||||
|
|
||||||
self.screenshotButton = QRightClickButton(Form)
|
|
||||||
self.screenshotButton.setGeometry(QtCore.QRect(455, 3, 55, 55))
|
|
||||||
self.screenshotButton.setObjectName("screenshotButton")
|
|
||||||
|
|
||||||
self.fileTransferButton = QtGui.QPushButton(Form)
|
|
||||||
self.fileTransferButton.setGeometry(QtCore.QRect(510, 3, 55, 55))
|
|
||||||
self.fileTransferButton.setObjectName("fileTransferButton")
|
|
||||||
|
|
||||||
self.sendMessageButton = QtGui.QPushButton(Form)
|
|
||||||
self.sendMessageButton.setGeometry(QtCore.QRect(565, 3, 60, 55))
|
|
||||||
self.sendMessageButton.setObjectName("sendMessageButton")
|
|
||||||
|
|
||||||
pixmap = QtGui.QPixmap(curr_directory() + '/images/send.png')
|
|
||||||
icon = QtGui.QIcon(pixmap)
|
|
||||||
self.sendMessageButton.setIcon(icon)
|
|
||||||
self.sendMessageButton.setIconSize(QtCore.QSize(45, 60))
|
|
||||||
pixmap = QtGui.QPixmap(curr_directory() + '/images/file.png')
|
|
||||||
icon = QtGui.QIcon(pixmap)
|
|
||||||
self.fileTransferButton.setIcon(icon)
|
|
||||||
self.fileTransferButton.setIconSize(QtCore.QSize(40, 40))
|
|
||||||
pixmap = QtGui.QPixmap(curr_directory() + '/images/screenshot.png')
|
|
||||||
icon = QtGui.QIcon(pixmap)
|
|
||||||
self.screenshotButton.setIcon(icon)
|
|
||||||
self.screenshotButton.setIconSize(QtCore.QSize(40, 60))
|
|
||||||
|
|
||||||
self.fileTransferButton.clicked.connect(self.send_file)
|
|
||||||
self.screenshotButton.clicked.connect(self.send_screenshot)
|
|
||||||
self.sendMessageButton.clicked.connect(self.send_message)
|
|
||||||
self.connect(self.screenshotButton, QtCore.SIGNAL("rightClicked()"), lambda: self.send_screenshot(True))
|
|
||||||
|
|
||||||
QtCore.QMetaObject.connectSlotsByName(Form)
|
|
||||||
|
|
||||||
def setup_left_center_menu(self, Form):
|
|
||||||
Form.resize(270, 25)
|
|
||||||
self.search_label = QtGui.QLabel(Form)
|
|
||||||
self.search_label.setGeometry(QtCore.QRect(3, 0, 25, 25))
|
|
||||||
pixmap = QtGui.QPixmap(QtCore.QSize(25, 25))
|
|
||||||
pixmap.load(curr_directory() + '/images/search.png')
|
|
||||||
self.search_label.setScaledContents(False)
|
|
||||||
self.search_label.setPixmap(pixmap.scaled(25, 25, QtCore.Qt.KeepAspectRatio))
|
|
||||||
|
|
||||||
self.contact_name = QtGui.QLineEdit(Form)
|
|
||||||
self.contact_name.setGeometry(QtCore.QRect(30, 0, 120, 25))
|
|
||||||
self.contact_name.setObjectName("contact_name")
|
|
||||||
self.contact_name.textChanged.connect(self.filtering)
|
|
||||||
self.online_contacts = QtGui.QComboBox(Form)
|
|
||||||
self.online_contacts.setGeometry(QtCore.QRect(150, 0, 120, 25))
|
|
||||||
self.online_contacts.activated[int].connect(lambda x: self.filtering())
|
|
||||||
|
|
||||||
QtCore.QMetaObject.connectSlotsByName(Form)
|
|
||||||
|
|
||||||
def setup_left_top(self, Form):
|
|
||||||
Form.setObjectName("left_top")
|
|
||||||
Form.setCursor(QtCore.Qt.PointingHandCursor)
|
|
||||||
Form.setMinimumSize(QtCore.QSize(250, 80))
|
|
||||||
Form.setMaximumSize(QtCore.QSize(250, 80))
|
|
||||||
Form.setBaseSize(QtCore.QSize(250, 80))
|
|
||||||
self.avatar_label = Form.avatar_label = QtGui.QLabel(Form)
|
|
||||||
self.avatar_label.setGeometry(QtCore.QRect(5, 15, 64, 64))
|
|
||||||
self.avatar_label.setScaledContents(True)
|
|
||||||
self.name = Form.name = DataLabel(Form)
|
|
||||||
Form.name.setGeometry(QtCore.QRect(80, 25, 150, 25))
|
|
||||||
font = QtGui.QFont()
|
|
||||||
font.setFamily("Times New Roman")
|
|
||||||
font.setPointSize(14)
|
|
||||||
font.setBold(True)
|
|
||||||
Form.name.setFont(font)
|
|
||||||
Form.name.setObjectName("name")
|
|
||||||
self.status_message = Form.status_message = DataLabel(Form)
|
|
||||||
Form.status_message.setGeometry(QtCore.QRect(80, 55, 170, 20))
|
|
||||||
font.setPointSize(12)
|
|
||||||
font.setBold(False)
|
|
||||||
Form.status_message.setFont(font)
|
|
||||||
Form.status_message.setObjectName("status_message")
|
|
||||||
self.connection_status = Form.connection_status = StatusCircle(self)
|
|
||||||
Form.connection_status.setGeometry(QtCore.QRect(230, 29, 64, 64))
|
|
||||||
Form.connection_status.setMinimumSize(QtCore.QSize(32, 32))
|
|
||||||
Form.connection_status.setMaximumSize(QtCore.QSize(32, 32))
|
|
||||||
Form.connection_status.setBaseSize(QtCore.QSize(32, 32))
|
|
||||||
self.avatar_label.mouseReleaseEvent = self.profile_settings
|
|
||||||
self.status_message.mouseReleaseEvent = self.profile_settings
|
|
||||||
self.name.mouseReleaseEvent = self.profile_settings
|
|
||||||
self.connection_status.raise_()
|
|
||||||
Form.connection_status.setObjectName("connection_status")
|
|
||||||
|
|
||||||
def setup_right_top(self, Form):
|
|
||||||
Form.setObjectName("Form")
|
|
||||||
Form.resize(650, 80)
|
|
||||||
self.account_avatar = QtGui.QLabel(Form)
|
|
||||||
self.account_avatar.setGeometry(QtCore.QRect(10, 17, 64, 64))
|
|
||||||
self.account_avatar.setScaledContents(True)
|
|
||||||
self.account_name = DataLabel(Form)
|
|
||||||
self.account_name.setGeometry(QtCore.QRect(100, 25, 400, 25))
|
|
||||||
self.account_name.setTextInteractionFlags(QtCore.Qt.LinksAccessibleByMouse)
|
|
||||||
font = QtGui.QFont()
|
|
||||||
font.setFamily("Times New Roman")
|
|
||||||
font.setPointSize(14)
|
|
||||||
font.setBold(True)
|
|
||||||
self.account_name.setFont(font)
|
|
||||||
self.account_name.setObjectName("account_name")
|
|
||||||
self.account_status = DataLabel(Form)
|
|
||||||
self.account_status.setGeometry(QtCore.QRect(100, 45, 400, 25))
|
|
||||||
self.account_status.setTextInteractionFlags(QtCore.Qt.LinksAccessibleByMouse)
|
|
||||||
font.setPointSize(12)
|
|
||||||
font.setBold(False)
|
|
||||||
self.account_status.setFont(font)
|
|
||||||
self.account_status.setObjectName("account_status")
|
|
||||||
self.callButton = QtGui.QPushButton(Form)
|
|
||||||
self.callButton.setGeometry(QtCore.QRect(550, 30, 50, 50))
|
|
||||||
self.callButton.setObjectName("callButton")
|
|
||||||
self.callButton.clicked.connect(self.call)
|
|
||||||
self.update_call_state('call')
|
|
||||||
self.typing = QtGui.QLabel(Form)
|
|
||||||
self.typing.setGeometry(QtCore.QRect(500, 40, 50, 30))
|
|
||||||
pixmap = QtGui.QPixmap(QtCore.QSize(50, 30))
|
|
||||||
pixmap.load(curr_directory() + '/images/typing.png')
|
|
||||||
self.typing.setScaledContents(False)
|
|
||||||
self.typing.setPixmap(pixmap.scaled(50, 30, QtCore.Qt.KeepAspectRatio))
|
|
||||||
self.typing.setVisible(False)
|
|
||||||
QtCore.QMetaObject.connectSlotsByName(Form)
|
|
||||||
|
|
||||||
def setup_left_center(self, widget):
|
|
||||||
self.friends_list = QtGui.QListWidget(widget)
|
|
||||||
self.friends_list.setObjectName("friends_list")
|
|
||||||
self.friends_list.setGeometry(0, 0, 270, 310)
|
|
||||||
self.friends_list.clicked.connect(self.friend_click)
|
|
||||||
self.friends_list.setContextMenuPolicy(QtCore.Qt.CustomContextMenu)
|
|
||||||
self.friends_list.connect(self.friends_list, QtCore.SIGNAL("customContextMenuRequested(QPoint)"),
|
|
||||||
self.friend_right_click)
|
|
||||||
self.friends_list.setVerticalScrollMode(QtGui.QAbstractItemView.ScrollPerPixel)
|
|
||||||
|
|
||||||
def setup_right_center(self, widget):
|
|
||||||
self.messages = QtGui.QListWidget(widget)
|
|
||||||
self.messages.setGeometry(0, 0, 620, 310)
|
|
||||||
self.messages.setObjectName("messages")
|
|
||||||
self.messages.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOn)
|
|
||||||
self.messages.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
|
|
||||||
self.messages.setFocusPolicy(QtCore.Qt.NoFocus)
|
|
||||||
|
|
||||||
def load(pos):
|
|
||||||
if not pos:
|
|
||||||
self.profile.load_history()
|
|
||||||
self.messages.verticalScrollBar().setValue(1)
|
|
||||||
self.messages.verticalScrollBar().valueChanged.connect(load)
|
|
||||||
self.messages.setVerticalScrollMode(QtGui.QAbstractItemView.ScrollPerPixel)
|
|
||||||
|
|
||||||
def initUI(self, tox):
|
|
||||||
self.setMinimumSize(920, 500)
|
|
||||||
self.setGeometry(400, 400, 920, 500)
|
|
||||||
self.setWindowTitle('Toxygen')
|
|
||||||
os.chdir(curr_directory() + '/images/')
|
|
||||||
main = QtGui.QWidget()
|
|
||||||
grid = QtGui.QGridLayout()
|
|
||||||
search = QtGui.QWidget()
|
|
||||||
self.setup_left_center_menu(search)
|
|
||||||
grid.addWidget(search, 1, 0)
|
|
||||||
name = QtGui.QWidget()
|
|
||||||
self.setup_left_top(name)
|
|
||||||
grid.addWidget(name, 0, 0)
|
|
||||||
messages = QtGui.QWidget()
|
|
||||||
self.setup_right_center(messages)
|
|
||||||
grid.addWidget(messages, 1, 1, 2, 1)
|
|
||||||
info = QtGui.QWidget()
|
|
||||||
self.setup_right_top(info)
|
|
||||||
grid.addWidget(info, 0, 1)
|
|
||||||
message_buttons = QtGui.QWidget()
|
|
||||||
self.setup_right_bottom(message_buttons)
|
|
||||||
grid.addWidget(message_buttons, 3, 1)
|
|
||||||
main_list = QtGui.QWidget()
|
|
||||||
self.setup_left_center(main_list)
|
|
||||||
grid.addWidget(main_list, 2, 0, 2, 1)
|
|
||||||
grid.setColumnMinimumWidth(1, 500)
|
|
||||||
grid.setColumnMinimumWidth(0, 270)
|
|
||||||
grid.setRowMinimumHeight(0, 82)
|
|
||||||
grid.setRowMinimumHeight(1, 25)
|
|
||||||
grid.setRowMinimumHeight(2, 410)
|
|
||||||
grid.setRowMinimumHeight(3, 60)
|
|
||||||
grid.setColumnStretch(1, 1)
|
|
||||||
grid.setRowStretch(1, 1)
|
|
||||||
main.setLayout(grid)
|
|
||||||
self.setCentralWidget(main)
|
|
||||||
self.setup_menu(self)
|
|
||||||
self.messageEdit.setFocus()
|
|
||||||
self.user_info = name
|
|
||||||
self.friend_info = info
|
|
||||||
self.retranslateUi()
|
|
||||||
self.profile = Profile(tox, self)
|
|
||||||
|
|
||||||
def closeEvent(self, *args, **kwargs):
|
|
||||||
self.profile.save_history()
|
|
||||||
self.profile.close()
|
|
||||||
QtGui.QApplication.closeAllWindows()
|
|
||||||
|
|
||||||
def resizeEvent(self, *args, **kwargs):
|
|
||||||
self.messages.setGeometry(0, 0, self.width() - 300, self.height() - 172)
|
|
||||||
self.friends_list.setGeometry(0, 0, 270, self.height() - 142)
|
|
||||||
self.callButton.setGeometry(QtCore.QRect(self.width() - 370, 20, 50, 50))
|
|
||||||
self.typing.setGeometry(QtCore.QRect(self.width() - 420, 30, 50, 30))
|
|
||||||
|
|
||||||
self.messageEdit.setGeometry(QtCore.QRect(120, 2, self.width() - 490, 55))
|
|
||||||
self.screenshotButton.setGeometry(QtCore.QRect(0, 2, 55, 55))
|
|
||||||
self.fileTransferButton.setGeometry(QtCore.QRect(60, 2, 55, 55))
|
|
||||||
self.sendMessageButton.setGeometry(QtCore.QRect(self.width() - 360, 2, 60, 55))
|
|
||||||
|
|
||||||
self.account_name.setGeometry(QtCore.QRect(100, 30, self.width() - 520, 25))
|
|
||||||
self.account_status.setGeometry(QtCore.QRect(100, 50, self.width() - 520, 25))
|
|
||||||
self.profile.update()
|
|
||||||
|
|
||||||
def keyPressEvent(self, event):
|
|
||||||
if event.key() == QtCore.Qt.Key_Escape:
|
|
||||||
self.hide()
|
|
||||||
else:
|
|
||||||
super(MainWindow, self).keyPressEvent(event)
|
|
||||||
|
|
||||||
# -----------------------------------------------------------------------------------------------------------------
|
|
||||||
# Functions which called when user click in menu
|
|
||||||
# -----------------------------------------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
def about_program(self):
|
|
||||||
import util
|
|
||||||
msgBox = QtGui.QMessageBox()
|
|
||||||
msgBox.setWindowTitle(QtGui.QApplication.translate("MainWindow", "About", None, QtGui.QApplication.UnicodeUTF8))
|
|
||||||
text = (QtGui.QApplication.translate("MainWindow", 'Toxygen is Tox client written on Python.\nVersion: ', None, QtGui.QApplication.UnicodeUTF8))
|
|
||||||
msgBox.setText(text + util.program_version + '\nGitHub: github.com/xveduk/toxygen/')
|
|
||||||
msgBox.exec_()
|
|
||||||
|
|
||||||
def network_settings(self):
|
|
||||||
self.n_s = NetworkSettings(self.reset)
|
|
||||||
self.n_s.show()
|
|
||||||
|
|
||||||
def plugins_menu(self):
|
|
||||||
self.p_s = PluginsSettings()
|
|
||||||
self.p_s.show()
|
|
||||||
|
|
||||||
def add_contact(self):
|
|
||||||
self.a_c = AddContact()
|
|
||||||
self.a_c.show()
|
|
||||||
|
|
||||||
def profile_settings(self, *args):
|
|
||||||
self.p_s = ProfileSettings()
|
|
||||||
self.p_s.show()
|
|
||||||
|
|
||||||
def privacy_settings(self):
|
|
||||||
self.priv_s = PrivacySettings()
|
|
||||||
self.priv_s.show()
|
|
||||||
|
|
||||||
def notification_settings(self):
|
|
||||||
self.notif_s = NotificationsSettings()
|
|
||||||
self.notif_s.show()
|
|
||||||
|
|
||||||
def interface_settings(self):
|
|
||||||
self.int_s = InterfaceSettings()
|
|
||||||
self.int_s.show()
|
|
||||||
|
|
||||||
def audio_settings(self):
|
|
||||||
self.audio_s = AudioSettings()
|
|
||||||
self.audio_s.show()
|
|
||||||
|
|
||||||
# -----------------------------------------------------------------------------------------------------------------
|
|
||||||
# Messages, calls and file transfers
|
|
||||||
# -----------------------------------------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
def send_message(self):
|
|
||||||
text = self.messageEdit.toPlainText()
|
|
||||||
self.profile.send_message(text)
|
|
||||||
|
|
||||||
def send_file(self):
|
|
||||||
if self.profile.is_active_online(): # active friend exists and online
|
|
||||||
choose_file = QtGui.QApplication.translate("MainWindow", 'Choose file', None, QtGui.QApplication.UnicodeUTF8)
|
|
||||||
choose = QtGui.QApplication.translate("MainWindow", choose_file, None, QtGui.QApplication.UnicodeUTF8)
|
|
||||||
name = QtGui.QFileDialog.getOpenFileName(self, choose)
|
|
||||||
if name[0]:
|
|
||||||
self.profile.send_file(name[0])
|
|
||||||
|
|
||||||
def send_screenshot(self, hide=False):
|
|
||||||
if self.profile.is_active_online(): # active friend exists and online
|
|
||||||
self.sw = ScreenShotWindow(self)
|
|
||||||
self.sw.show()
|
|
||||||
if hide:
|
|
||||||
self.hide()
|
|
||||||
|
|
||||||
def call(self):
|
|
||||||
if self.profile.is_active_online(): # active friend exists and online
|
|
||||||
self.profile.call_click(True)
|
|
||||||
|
|
||||||
def active_call(self):
|
|
||||||
self.update_call_state('finish_call')
|
|
||||||
|
|
||||||
def incoming_call(self):
|
|
||||||
self.update_call_state('incoming_call')
|
|
||||||
|
|
||||||
def call_finished(self):
|
|
||||||
self.update_call_state('call')
|
|
||||||
|
|
||||||
def update_call_state(self, fl):
|
|
||||||
pixmap = QtGui.QPixmap(curr_directory() + '/images/{}.png'.format(fl))
|
|
||||||
icon = QtGui.QIcon(pixmap)
|
|
||||||
self.callButton.setIcon(icon)
|
|
||||||
self.callButton.setIconSize(QtCore.QSize(50, 50))
|
|
||||||
|
|
||||||
# -----------------------------------------------------------------------------------------------------------------
|
|
||||||
# Functions which called when user open context menu in friends list
|
|
||||||
# -----------------------------------------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
def friend_right_click(self, pos):
|
|
||||||
item = self.friends_list.itemAt(pos)
|
|
||||||
num = self.friends_list.indexFromItem(item).row()
|
|
||||||
friend = Profile.get_instance().get_friend(num)
|
|
||||||
settings = Settings.get_instance()
|
|
||||||
allowed = friend.tox_id in settings['auto_accept_from_friends']
|
|
||||||
auto = QtGui.QApplication.translate("MainWindow", 'Disallow auto accept', None, QtGui.QApplication.UnicodeUTF8) if allowed else QtGui.QApplication.translate("MainWindow", 'Allow auto accept', None, QtGui.QApplication.UnicodeUTF8)
|
|
||||||
if item is not None:
|
|
||||||
self.listMenu = QtGui.QMenu()
|
|
||||||
set_alias_item = self.listMenu.addAction(QtGui.QApplication.translate("MainWindow", 'Set alias', None, QtGui.QApplication.UnicodeUTF8))
|
|
||||||
clear_history_item = self.listMenu.addAction(QtGui.QApplication.translate("MainWindow", 'Clear history', None, QtGui.QApplication.UnicodeUTF8))
|
|
||||||
copy_key_item = self.listMenu.addAction(QtGui.QApplication.translate("MainWindow", 'Copy public key', None, QtGui.QApplication.UnicodeUTF8))
|
|
||||||
auto_accept_item = self.listMenu.addAction(auto)
|
|
||||||
remove_item = self.listMenu.addAction(QtGui.QApplication.translate("MainWindow", 'Remove friend', None, QtGui.QApplication.UnicodeUTF8))
|
|
||||||
submenu = plugin_support.PluginLoader.get_instance().get_menu(self.listMenu, num)
|
|
||||||
if len(submenu):
|
|
||||||
plug = self.listMenu.addMenu(QtGui.QApplication.translate("MainWindow", 'Plugins', None, QtGui.QApplication.UnicodeUTF8))
|
|
||||||
plug.addActions(submenu)
|
|
||||||
self.connect(set_alias_item, QtCore.SIGNAL("triggered()"), lambda: self.set_alias(num))
|
|
||||||
self.connect(remove_item, QtCore.SIGNAL("triggered()"), lambda: self.remove_friend(num))
|
|
||||||
self.connect(copy_key_item, QtCore.SIGNAL("triggered()"), lambda: self.copy_friend_key(num))
|
|
||||||
self.connect(clear_history_item, QtCore.SIGNAL("triggered()"), lambda: self.clear_history(num))
|
|
||||||
self.connect(auto_accept_item, QtCore.SIGNAL("triggered()"), lambda: self.auto_accept(num, not allowed))
|
|
||||||
parent_position = self.friends_list.mapToGlobal(QtCore.QPoint(0, 0))
|
|
||||||
self.listMenu.move(parent_position + pos)
|
|
||||||
self.listMenu.show()
|
|
||||||
|
|
||||||
def set_alias(self, num):
|
|
||||||
self.profile.set_alias(num)
|
|
||||||
|
|
||||||
def remove_friend(self, num):
|
|
||||||
self.profile.delete_friend(num)
|
|
||||||
|
|
||||||
def copy_friend_key(self, num):
|
|
||||||
tox_id = self.profile.friend_public_key(num)
|
|
||||||
clipboard = QtGui.QApplication.clipboard()
|
|
||||||
clipboard.setText(tox_id)
|
|
||||||
|
|
||||||
def clear_history(self, num):
|
|
||||||
self.profile.clear_history(num)
|
|
||||||
|
|
||||||
def auto_accept(self, num, value):
|
|
||||||
settings = Settings.get_instance()
|
|
||||||
tox_id = self.profile.friend_public_key(num)
|
|
||||||
if value:
|
|
||||||
settings['auto_accept_from_friends'].append(tox_id)
|
|
||||||
else:
|
|
||||||
settings['auto_accept_from_friends'].remove(tox_id)
|
|
||||||
settings.save()
|
|
||||||
|
|
||||||
# -----------------------------------------------------------------------------------------------------------------
|
|
||||||
# Functions which called when user click somewhere else
|
|
||||||
# -----------------------------------------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
def friend_click(self, index):
|
|
||||||
num = index.row()
|
|
||||||
self.profile.set_active(num)
|
|
||||||
|
|
||||||
def mouseReleaseEvent(self, event):
|
|
||||||
x, y = event.x(), event.y()
|
|
||||||
pos = self.connection_status.pos()
|
|
||||||
if (pos.x() < x < pos.x() + 32) and (pos.y() < y < pos.y() + 32):
|
|
||||||
self.profile.change_status()
|
|
||||||
else:
|
|
||||||
super(self.__class__, self).mouseReleaseEvent(event)
|
|
||||||
|
|
||||||
def filtering(self):
|
|
||||||
self.profile.filtration(self.online_contacts.currentIndex() == 1, self.contact_name.text())
|
|
||||||
|
|
||||||
|
|
||||||
class ScreenShotWindow(QtGui.QWidget):
|
|
||||||
|
|
||||||
def __init__(self, parent):
|
|
||||||
super(ScreenShotWindow, self).__init__()
|
|
||||||
self.parent = parent
|
|
||||||
self.setMouseTracking(True)
|
|
||||||
self.setWindowFlags(self.windowFlags() | QtCore.Qt.FramelessWindowHint | QtCore.Qt.WindowStaysOnTopHint)
|
|
||||||
self.showFullScreen()
|
|
||||||
self.setWindowOpacity(0.5)
|
|
||||||
self.rubberband = QtGui.QRubberBand(QtGui.QRubberBand.Rectangle, None)
|
|
||||||
|
|
||||||
def closeEvent(self, *args):
|
|
||||||
if self.parent.isHidden():
|
|
||||||
self.parent.show()
|
|
||||||
|
|
||||||
def mousePressEvent(self, event):
|
|
||||||
self.origin = event.pos()
|
|
||||||
self.rubberband.setGeometry(QtCore.QRect(self.origin, QtCore.QSize()))
|
|
||||||
self.rubberband.show()
|
|
||||||
QtGui.QWidget.mousePressEvent(self, event)
|
|
||||||
|
|
||||||
def mouseMoveEvent(self, event):
|
|
||||||
if self.rubberband.isVisible():
|
|
||||||
self.rubberband.setGeometry(QtCore.QRect(self.origin, event.pos()).normalized())
|
|
||||||
left = QtGui.QRegion(QtCore.QRect(0, 0, self.rubberband.x(), self.height()))
|
|
||||||
right = QtGui.QRegion(QtCore.QRect(self.rubberband.x() + self.rubberband.width(), 0, self.width(), self.height()))
|
|
||||||
top = QtGui.QRegion(0, 0, self.width(), self.rubberband.y())
|
|
||||||
bottom = QtGui.QRegion(0, self.rubberband.y() + self.rubberband.height(), self.width(), self.height())
|
|
||||||
self.setMask(left + right + top + bottom)
|
|
||||||
|
|
||||||
def mouseReleaseEvent(self, event):
|
|
||||||
if self.rubberband.isVisible():
|
|
||||||
self.rubberband.hide()
|
|
||||||
rect = self.rubberband.geometry()
|
|
||||||
print rect
|
|
||||||
if rect.width() and rect.height():
|
|
||||||
p = QtGui.QPixmap.grabWindow(QtGui.QApplication.desktop().winId(),
|
|
||||||
rect.x() + 4,
|
|
||||||
rect.y() + 4,
|
|
||||||
rect.width() - 8,
|
|
||||||
rect.height() - 8)
|
|
||||||
byte_array = QtCore.QByteArray()
|
|
||||||
buffer = QtCore.QBuffer(byte_array)
|
|
||||||
buffer.open(QtCore.QIODevice.WriteOnly)
|
|
||||||
p.save(buffer, 'PNG')
|
|
||||||
Profile.get_instance().send_screenshot(str(byte_array.data()))
|
|
||||||
self.close()
|
|
||||||
|
|
||||||
def keyPressEvent(self, event):
|
|
||||||
if event.key() == QtCore.Qt.Key_Escape:
|
|
||||||
self.rubberband.setHidden(True)
|
|
||||||
self.close()
|
|
||||||
else:
|
|
||||||
super(ScreenShotWindow, self).keyPressEvent(event)
|
|
||||||
|
|
||||||
|
|
||||||
|
|
665
src/menu.py
@ -1,665 +0,0 @@
|
|||||||
try:
|
|
||||||
from PySide import QtCore, QtGui
|
|
||||||
except ImportError:
|
|
||||||
from PyQt4 import QtCore, QtGui
|
|
||||||
from settings import *
|
|
||||||
from profile import Profile
|
|
||||||
from util import get_style, curr_directory
|
|
||||||
from widgets import CenteredWidget, DataLabel
|
|
||||||
import pyaudio
|
|
||||||
import toxencryptsave
|
|
||||||
import plugin_support
|
|
||||||
|
|
||||||
|
|
||||||
class AddContact(CenteredWidget):
|
|
||||||
"""Add contact form"""
|
|
||||||
|
|
||||||
def __init__(self):
|
|
||||||
super(AddContact, self).__init__()
|
|
||||||
self.initUI()
|
|
||||||
|
|
||||||
def initUI(self):
|
|
||||||
self.setObjectName('AddContact')
|
|
||||||
self.resize(568, 306)
|
|
||||||
self.sendRequestButton = QtGui.QPushButton(self)
|
|
||||||
self.sendRequestButton.setGeometry(QtCore.QRect(50, 270, 471, 31))
|
|
||||||
self.sendRequestButton.setMinimumSize(QtCore.QSize(0, 0))
|
|
||||||
self.sendRequestButton.setBaseSize(QtCore.QSize(0, 0))
|
|
||||||
self.sendRequestButton.setObjectName("sendRequestButton")
|
|
||||||
self.sendRequestButton.clicked.connect(self.add_friend)
|
|
||||||
self.tox_id = QtGui.QLineEdit(self)
|
|
||||||
self.tox_id.setGeometry(QtCore.QRect(50, 40, 471, 27))
|
|
||||||
self.tox_id.setObjectName("lineEdit")
|
|
||||||
self.label = QtGui.QLabel(self)
|
|
||||||
self.label.setGeometry(QtCore.QRect(60, 10, 80, 20))
|
|
||||||
self.error_label = DataLabel(self)
|
|
||||||
self.error_label.setGeometry(QtCore.QRect(120, 10, 420, 20))
|
|
||||||
font = QtGui.QFont()
|
|
||||||
font.setPointSize(10)
|
|
||||||
font.setWeight(30)
|
|
||||||
self.error_label.setFont(font)
|
|
||||||
self.error_label.setStyleSheet("QLabel { color: red; }")
|
|
||||||
self.label.setObjectName("label")
|
|
||||||
self.message_edit = QtGui.QTextEdit(self)
|
|
||||||
self.message_edit.setGeometry(QtCore.QRect(50, 110, 471, 151))
|
|
||||||
self.message_edit.setObjectName("textEdit")
|
|
||||||
self.message = QtGui.QLabel(self)
|
|
||||||
self.message.setGeometry(QtCore.QRect(60, 70, 101, 31))
|
|
||||||
self.message.setFont(font)
|
|
||||||
self.message.setObjectName("label_2")
|
|
||||||
self.retranslateUi()
|
|
||||||
self.message_edit.setText('Hello! Add me to your contact list please')
|
|
||||||
font = QtGui.QFont()
|
|
||||||
font.setPointSize(12)
|
|
||||||
font.setBold(True)
|
|
||||||
self.label.setFont(font)
|
|
||||||
self.message.setFont(font)
|
|
||||||
QtCore.QMetaObject.connectSlotsByName(self)
|
|
||||||
|
|
||||||
def add_friend(self):
|
|
||||||
profile = Profile.get_instance()
|
|
||||||
send = profile.send_friend_request(self.tox_id.text(), self.message_edit.toPlainText())
|
|
||||||
if send is True:
|
|
||||||
# request was successful
|
|
||||||
self.close()
|
|
||||||
else: # print error data
|
|
||||||
self.error_label.setText(send)
|
|
||||||
|
|
||||||
def retranslateUi(self):
|
|
||||||
self.setWindowTitle(QtGui.QApplication.translate('AddContact', "Add contact", None, QtGui.QApplication.UnicodeUTF8))
|
|
||||||
self.sendRequestButton.setText(QtGui.QApplication.translate("Form", "Send request", None, QtGui.QApplication.UnicodeUTF8))
|
|
||||||
self.label.setText(QtGui.QApplication.translate('AddContact', "TOX ID:", None, QtGui.QApplication.UnicodeUTF8))
|
|
||||||
self.message.setText(QtGui.QApplication.translate('AddContact', "Message:", None, QtGui.QApplication.UnicodeUTF8))
|
|
||||||
|
|
||||||
|
|
||||||
class ProfileSettings(CenteredWidget):
|
|
||||||
"""Form with profile settings such as name, status, TOX ID"""
|
|
||||||
def __init__(self):
|
|
||||||
super(ProfileSettings, self).__init__()
|
|
||||||
self.initUI()
|
|
||||||
self.center()
|
|
||||||
|
|
||||||
def initUI(self):
|
|
||||||
self.setObjectName("ProfileSettingsForm")
|
|
||||||
self.setMinimumSize(QtCore.QSize(650, 520))
|
|
||||||
self.setMaximumSize(QtCore.QSize(650, 520))
|
|
||||||
self.nick = QtGui.QLineEdit(self)
|
|
||||||
self.nick.setGeometry(QtCore.QRect(30, 60, 350, 27))
|
|
||||||
self.nick.setObjectName("nick")
|
|
||||||
profile = Profile.get_instance()
|
|
||||||
self.nick.setText(profile.name)
|
|
||||||
self.status = QtGui.QLineEdit(self)
|
|
||||||
self.status.setGeometry(QtCore.QRect(30, 130, 350, 27))
|
|
||||||
self.status.setObjectName("status")
|
|
||||||
self.status.setText(profile.status_message)
|
|
||||||
self.label = QtGui.QLabel(self)
|
|
||||||
self.label.setGeometry(QtCore.QRect(40, 30, 91, 25))
|
|
||||||
font = QtGui.QFont()
|
|
||||||
font.setPointSize(18)
|
|
||||||
font.setWeight(75)
|
|
||||||
font.setBold(True)
|
|
||||||
self.label.setFont(font)
|
|
||||||
self.label.setObjectName("label")
|
|
||||||
self.label_2 = QtGui.QLabel(self)
|
|
||||||
self.label_2.setGeometry(QtCore.QRect(40, 100, 100, 25))
|
|
||||||
self.label_2.setFont(font)
|
|
||||||
self.label_2.setObjectName("label_2")
|
|
||||||
self.label_3 = QtGui.QLabel(self)
|
|
||||||
self.label_3.setGeometry(QtCore.QRect(40, 180, 100, 25))
|
|
||||||
self.label_3.setFont(font)
|
|
||||||
self.label_3.setObjectName("label_3")
|
|
||||||
self.tox_id = QtGui.QLabel(self)
|
|
||||||
self.tox_id.setGeometry(QtCore.QRect(10, 210, self.width(), 21))
|
|
||||||
font.setPointSize(10)
|
|
||||||
self.tox_id.setFont(font)
|
|
||||||
self.tox_id.setObjectName("tox_id")
|
|
||||||
s = profile.tox_id
|
|
||||||
self.tox_id.setText(s)
|
|
||||||
self.copyId = QtGui.QPushButton(self)
|
|
||||||
self.copyId.setGeometry(QtCore.QRect(40, 250, 160, 30))
|
|
||||||
self.copyId.setObjectName("copyId")
|
|
||||||
self.copyId.clicked.connect(self.copy)
|
|
||||||
self.export = QtGui.QPushButton(self)
|
|
||||||
self.export.setGeometry(QtCore.QRect(210, 250, 160, 30))
|
|
||||||
self.export.setObjectName("export")
|
|
||||||
self.export.clicked.connect(self.export_profile)
|
|
||||||
self.new_nospam = QtGui.QPushButton(self)
|
|
||||||
self.new_nospam.setGeometry(QtCore.QRect(380, 250, 160, 30))
|
|
||||||
self.new_nospam.clicked.connect(self.new_no_spam)
|
|
||||||
self.new_avatar = QtGui.QPushButton(self)
|
|
||||||
self.new_avatar.setGeometry(QtCore.QRect(400, 50, 200, 50))
|
|
||||||
self.delete_avatar = QtGui.QPushButton(self)
|
|
||||||
self.delete_avatar.setGeometry(QtCore.QRect(400, 120, 200, 50))
|
|
||||||
self.delete_avatar.clicked.connect(self.reset_avatar)
|
|
||||||
self.new_avatar.clicked.connect(self.set_avatar)
|
|
||||||
self.profile_pass = QtGui.QLabel(self)
|
|
||||||
self.profile_pass.setGeometry(QtCore.QRect(40, 300, 300, 50))
|
|
||||||
font.setPointSize(18)
|
|
||||||
self.profile_pass.setFont(font)
|
|
||||||
self.password = QtGui.QLineEdit(self)
|
|
||||||
self.password.setGeometry(QtCore.QRect(30, 350, 300, 30))
|
|
||||||
self.password.setEchoMode(QtGui.QLineEdit.EchoMode.Password)
|
|
||||||
self.leave_blank = QtGui.QLabel(self)
|
|
||||||
self.leave_blank.setGeometry(QtCore.QRect(340, 350, 300, 30))
|
|
||||||
self.confirm_password = QtGui.QLineEdit(self)
|
|
||||||
self.confirm_password.setGeometry(QtCore.QRect(30, 400, 300, 30))
|
|
||||||
self.confirm_password.setEchoMode(QtGui.QLineEdit.EchoMode.Password)
|
|
||||||
self.set_password = QtGui.QPushButton(self)
|
|
||||||
self.set_password.setGeometry(QtCore.QRect(30, 450, 300, 30))
|
|
||||||
self.set_password.clicked.connect(self.new_password)
|
|
||||||
self.not_match = QtGui.QLabel(self)
|
|
||||||
self.not_match.setGeometry(QtCore.QRect(340, 400, 300, 30))
|
|
||||||
self.not_match.setVisible(False)
|
|
||||||
self.not_match.setStyleSheet('QLabel { color: #F70D1A; }')
|
|
||||||
self.warning = QtGui.QLabel(self)
|
|
||||||
self.warning.setGeometry(QtCore.QRect(30, 490, 500, 30))
|
|
||||||
self.warning.setStyleSheet('QLabel { color: #F70D1A; }')
|
|
||||||
self.retranslateUi()
|
|
||||||
QtCore.QMetaObject.connectSlotsByName(self)
|
|
||||||
|
|
||||||
def retranslateUi(self):
|
|
||||||
self.export.setText(QtGui.QApplication.translate("ProfileSettingsForm", "Export profile", None, QtGui.QApplication.UnicodeUTF8))
|
|
||||||
self.setWindowTitle(QtGui.QApplication.translate("ProfileSettingsForm", "Profile settings", None, QtGui.QApplication.UnicodeUTF8))
|
|
||||||
self.label.setText(QtGui.QApplication.translate("ProfileSettingsForm", "Name:", None, QtGui.QApplication.UnicodeUTF8))
|
|
||||||
self.label_2.setText(QtGui.QApplication.translate("ProfileSettingsForm", "Status:", None, QtGui.QApplication.UnicodeUTF8))
|
|
||||||
self.label_3.setText(QtGui.QApplication.translate("ProfileSettingsForm", "TOX ID:", None, QtGui.QApplication.UnicodeUTF8))
|
|
||||||
self.copyId.setText(QtGui.QApplication.translate("ProfileSettingsForm", "Copy TOX ID", None, QtGui.QApplication.UnicodeUTF8))
|
|
||||||
self.new_avatar.setText(QtGui.QApplication.translate("ProfileSettingsForm", "New avatar", None, QtGui.QApplication.UnicodeUTF8))
|
|
||||||
self.delete_avatar.setText(QtGui.QApplication.translate("ProfileSettingsForm", "Reset avatar", None, QtGui.QApplication.UnicodeUTF8))
|
|
||||||
self.new_nospam.setText(QtGui.QApplication.translate("ProfileSettingsForm", "New NoSpam", None, QtGui.QApplication.UnicodeUTF8))
|
|
||||||
self.profile_pass.setText(QtGui.QApplication.translate("ProfileSettingsForm", "Profile password", None, QtGui.QApplication.UnicodeUTF8))
|
|
||||||
self.password.setPlaceholderText(QtGui.QApplication.translate("ProfileSettingsForm", "Password (at least 8 symbols)", None, QtGui.QApplication.UnicodeUTF8))
|
|
||||||
self.confirm_password.setPlaceholderText(QtGui.QApplication.translate("ProfileSettingsForm", "Confirm password", None, QtGui.QApplication.UnicodeUTF8))
|
|
||||||
self.set_password.setText(QtGui.QApplication.translate("ProfileSettingsForm", "Set password", None, QtGui.QApplication.UnicodeUTF8))
|
|
||||||
self.not_match.setText(QtGui.QApplication.translate("ProfileSettingsForm", "Passwords do not match", None, QtGui.QApplication.UnicodeUTF8))
|
|
||||||
self.leave_blank.setText(QtGui.QApplication.translate("ProfileSettingsForm", "Leaving blank will reset current password", None, QtGui.QApplication.UnicodeUTF8))
|
|
||||||
self.warning.setText(QtGui.QApplication.translate("ProfileSettingsForm", "There is no way to recover lost passwords", None, QtGui.QApplication.UnicodeUTF8))
|
|
||||||
|
|
||||||
def new_password(self):
|
|
||||||
if self.password.text() == self.confirm_password.text():
|
|
||||||
if not len(self.password.text()) or len(self.password.text()) >= 8:
|
|
||||||
e = toxencryptsave.LibToxEncryptSave.get_instance()
|
|
||||||
e.set_password(self.password.text())
|
|
||||||
self.close()
|
|
||||||
else:
|
|
||||||
self.not_match.setText(
|
|
||||||
QtGui.QApplication.translate("ProfileSettingsForm", "Password must be at least 8 symbols", None,
|
|
||||||
QtGui.QApplication.UnicodeUTF8))
|
|
||||||
self.not_match.setVisible(True)
|
|
||||||
else:
|
|
||||||
self.not_match.setText(QtGui.QApplication.translate("ProfileSettingsForm", "Passwords do not match", None,
|
|
||||||
QtGui.QApplication.UnicodeUTF8))
|
|
||||||
self.not_match.setVisible(True)
|
|
||||||
|
|
||||||
def copy(self):
|
|
||||||
clipboard = QtGui.QApplication.clipboard()
|
|
||||||
profile = Profile.get_instance()
|
|
||||||
clipboard.setText(profile.tox_id)
|
|
||||||
pixmap = QtGui.QPixmap(curr_directory() + '/images/accept.png')
|
|
||||||
icon = QtGui.QIcon(pixmap)
|
|
||||||
self.copyId.setIcon(icon)
|
|
||||||
self.copyId.setIconSize(QtCore.QSize(10, 10))
|
|
||||||
|
|
||||||
def new_no_spam(self):
|
|
||||||
self.tox_id.setText(Profile.get_instance().new_nospam())
|
|
||||||
|
|
||||||
def reset_avatar(self):
|
|
||||||
Profile.get_instance().reset_avatar()
|
|
||||||
|
|
||||||
def set_avatar(self):
|
|
||||||
choose = QtGui.QApplication.translate("ProfileSettingsForm", "Choose avatar", None, QtGui.QApplication.UnicodeUTF8)
|
|
||||||
name = QtGui.QFileDialog.getOpenFileName(self, choose, None, 'Image Files (*.png)')
|
|
||||||
if name[0]:
|
|
||||||
with open(name[0], 'rb') as f:
|
|
||||||
data = f.read()
|
|
||||||
Profile.get_instance().set_avatar(data)
|
|
||||||
|
|
||||||
def export_profile(self):
|
|
||||||
directory = QtGui.QFileDialog.getExistingDirectory() + '/'
|
|
||||||
if directory != '/':
|
|
||||||
ProfileHelper.get_instance().export_profile(directory)
|
|
||||||
settings = Settings.get_instance()
|
|
||||||
settings.export(directory)
|
|
||||||
profile = Profile.get_instance()
|
|
||||||
profile.export_history(directory)
|
|
||||||
|
|
||||||
def closeEvent(self, event):
|
|
||||||
profile = Profile.get_instance()
|
|
||||||
profile.set_name(self.nick.text().encode('utf-8'))
|
|
||||||
profile.set_status_message(self.status.text().encode('utf-8'))
|
|
||||||
|
|
||||||
|
|
||||||
class NetworkSettings(CenteredWidget):
|
|
||||||
"""Network settings form: UDP, Ipv6 and proxy"""
|
|
||||||
def __init__(self, reset):
|
|
||||||
super(NetworkSettings, self).__init__()
|
|
||||||
self.reset = reset
|
|
||||||
self.initUI()
|
|
||||||
self.center()
|
|
||||||
|
|
||||||
def initUI(self):
|
|
||||||
self.setObjectName("NetworkSettings")
|
|
||||||
self.resize(300, 330)
|
|
||||||
self.setMinimumSize(QtCore.QSize(300, 330))
|
|
||||||
self.setMaximumSize(QtCore.QSize(300, 330))
|
|
||||||
self.setBaseSize(QtCore.QSize(300, 330))
|
|
||||||
self.ipv = QtGui.QCheckBox(self)
|
|
||||||
self.ipv.setGeometry(QtCore.QRect(20, 10, 97, 22))
|
|
||||||
self.ipv.setObjectName("ipv")
|
|
||||||
self.udp = QtGui.QCheckBox(self)
|
|
||||||
self.udp.setGeometry(QtCore.QRect(150, 10, 97, 22))
|
|
||||||
self.udp.setObjectName("udp")
|
|
||||||
self.proxy = QtGui.QCheckBox(self)
|
|
||||||
self.proxy.setGeometry(QtCore.QRect(20, 40, 97, 22))
|
|
||||||
self.http = QtGui.QCheckBox(self)
|
|
||||||
self.http.setGeometry(QtCore.QRect(20, 70, 97, 22))
|
|
||||||
self.proxy.setObjectName("proxy")
|
|
||||||
self.proxyip = QtGui.QLineEdit(self)
|
|
||||||
self.proxyip.setGeometry(QtCore.QRect(40, 130, 231, 27))
|
|
||||||
self.proxyip.setObjectName("proxyip")
|
|
||||||
self.proxyport = QtGui.QLineEdit(self)
|
|
||||||
self.proxyport.setGeometry(QtCore.QRect(40, 190, 231, 27))
|
|
||||||
self.proxyport.setObjectName("proxyport")
|
|
||||||
self.label = QtGui.QLabel(self)
|
|
||||||
self.label.setGeometry(QtCore.QRect(40, 100, 66, 17))
|
|
||||||
self.label_2 = QtGui.QLabel(self)
|
|
||||||
self.label_2.setGeometry(QtCore.QRect(40, 165, 66, 17))
|
|
||||||
self.reconnect = QtGui.QPushButton(self)
|
|
||||||
self.reconnect.setGeometry(QtCore.QRect(40, 230, 231, 30))
|
|
||||||
self.reconnect.clicked.connect(self.restart_core)
|
|
||||||
settings = Settings.get_instance()
|
|
||||||
self.ipv.setChecked(settings['ipv6_enabled'])
|
|
||||||
self.udp.setChecked(settings['udp_enabled'])
|
|
||||||
self.proxy.setChecked(settings['proxy_type'])
|
|
||||||
self.proxyip.setText(settings['proxy_host'])
|
|
||||||
self.proxyport.setText(unicode(settings['proxy_port']))
|
|
||||||
self.http.setChecked(settings['proxy_type'] == 1)
|
|
||||||
self.warning = QtGui.QLabel(self)
|
|
||||||
self.warning.setGeometry(QtCore.QRect(5, 270, 290, 60))
|
|
||||||
self.warning.setStyleSheet('QLabel { color: #F70D1A; }')
|
|
||||||
self.retranslateUi()
|
|
||||||
self.proxy.stateChanged.connect(lambda x: self.activate())
|
|
||||||
self.activate()
|
|
||||||
QtCore.QMetaObject.connectSlotsByName(self)
|
|
||||||
|
|
||||||
def retranslateUi(self):
|
|
||||||
self.setWindowTitle(QtGui.QApplication.translate("NetworkSettings", "Network settings", None, QtGui.QApplication.UnicodeUTF8))
|
|
||||||
self.ipv.setText(QtGui.QApplication.translate("Form", "IPv6", None, QtGui.QApplication.UnicodeUTF8))
|
|
||||||
self.udp.setText(QtGui.QApplication.translate("Form", "UDP", None, QtGui.QApplication.UnicodeUTF8))
|
|
||||||
self.proxy.setText(QtGui.QApplication.translate("Form", "Proxy", None, QtGui.QApplication.UnicodeUTF8))
|
|
||||||
self.label.setText(QtGui.QApplication.translate("Form", "IP:", None, QtGui.QApplication.UnicodeUTF8))
|
|
||||||
self.label_2.setText(QtGui.QApplication.translate("Form", "Port:", None, QtGui.QApplication.UnicodeUTF8))
|
|
||||||
self.reconnect.setText(QtGui.QApplication.translate("NetworkSettings", "Restart TOX core", None, QtGui.QApplication.UnicodeUTF8))
|
|
||||||
self.http.setText(QtGui.QApplication.translate("Form", "HTTP", None, QtGui.QApplication.UnicodeUTF8))
|
|
||||||
self.warning.setText(QtGui.QApplication.translate("Form", "WARNING:\nusing proxy with enabled UDP\ncan produce IP leak",
|
|
||||||
None, QtGui.QApplication.UnicodeUTF8))
|
|
||||||
|
|
||||||
def activate(self):
|
|
||||||
bl = self.proxy.isChecked()
|
|
||||||
self.proxyip.setEnabled(bl)
|
|
||||||
self.http.setEnabled(bl)
|
|
||||||
self.proxyport.setEnabled(bl)
|
|
||||||
|
|
||||||
def restart_core(self):
|
|
||||||
try:
|
|
||||||
settings = Settings.get_instance()
|
|
||||||
settings['ipv6_enabled'] = self.ipv.isChecked()
|
|
||||||
settings['udp_enabled'] = self.udp.isChecked()
|
|
||||||
settings['proxy_type'] = 2 - int(self.http.isChecked()) if self.proxy.isChecked() else 0
|
|
||||||
settings['proxy_host'] = str(self.proxyip.text())
|
|
||||||
settings['proxy_port'] = int(self.proxyport.text())
|
|
||||||
settings.save()
|
|
||||||
# recreate tox instance
|
|
||||||
Profile.get_instance().reset(self.reset)
|
|
||||||
self.close()
|
|
||||||
except Exception as ex:
|
|
||||||
log('Exception in restart: ' + str(ex))
|
|
||||||
|
|
||||||
|
|
||||||
class PrivacySettings(CenteredWidget):
|
|
||||||
"""Privacy settings form: history, typing notifications"""
|
|
||||||
|
|
||||||
def __init__(self):
|
|
||||||
super(PrivacySettings, self).__init__()
|
|
||||||
self.initUI()
|
|
||||||
self.center()
|
|
||||||
|
|
||||||
def initUI(self):
|
|
||||||
self.setObjectName("privacySettings")
|
|
||||||
self.resize(350, 550)
|
|
||||||
self.setMinimumSize(QtCore.QSize(350, 550))
|
|
||||||
self.setMaximumSize(QtCore.QSize(350, 550))
|
|
||||||
self.saveHistory = QtGui.QCheckBox(self)
|
|
||||||
self.saveHistory.setGeometry(QtCore.QRect(10, 20, 291, 22))
|
|
||||||
self.saveHistory.setObjectName("saveHistory")
|
|
||||||
self.fileautoaccept = QtGui.QCheckBox(self)
|
|
||||||
self.fileautoaccept.setGeometry(QtCore.QRect(10, 60, 271, 22))
|
|
||||||
self.fileautoaccept.setObjectName("fileautoaccept")
|
|
||||||
self.typingNotifications = QtGui.QCheckBox(self)
|
|
||||||
self.typingNotifications.setGeometry(QtCore.QRect(10, 100, 350, 30))
|
|
||||||
self.typingNotifications.setObjectName("typingNotifications")
|
|
||||||
self.inlines = QtGui.QCheckBox(self)
|
|
||||||
self.inlines.setGeometry(QtCore.QRect(10, 140, 350, 30))
|
|
||||||
self.inlines.setObjectName("inlines")
|
|
||||||
|
|
||||||
|
|
||||||
self.auto_path = QtGui.QLabel(self)
|
|
||||||
self.auto_path.setGeometry(QtCore.QRect(10, 190, 350, 30))
|
|
||||||
self.path = QtGui.QPlainTextEdit(self)
|
|
||||||
self.path.setGeometry(QtCore.QRect(10, 225, 330, 45))
|
|
||||||
self.change_path = QtGui.QPushButton(self)
|
|
||||||
self.change_path.setGeometry(QtCore.QRect(10, 280, 330, 30))
|
|
||||||
settings = Settings.get_instance()
|
|
||||||
self.typingNotifications.setChecked(settings['typing_notifications'])
|
|
||||||
self.fileautoaccept.setChecked(settings['allow_auto_accept'])
|
|
||||||
self.saveHistory.setChecked(settings['save_history'])
|
|
||||||
self.inlines.setChecked(settings['allow_inline'])
|
|
||||||
self.path.setPlainText(settings['auto_accept_path'] or curr_directory())
|
|
||||||
self.change_path.clicked.connect(self.new_path)
|
|
||||||
self.block_user_label = QtGui.QLabel(self)
|
|
||||||
self.block_user_label.setGeometry(QtCore.QRect(10, 320, 330, 30))
|
|
||||||
self.block_id = QtGui.QPlainTextEdit(self)
|
|
||||||
self.block_id.setGeometry(QtCore.QRect(10, 350, 330, 30))
|
|
||||||
self.block = QtGui.QPushButton(self)
|
|
||||||
self.block.setGeometry(QtCore.QRect(10, 390, 330, 30))
|
|
||||||
self.block.clicked.connect(lambda: Profile.get_instance().block_user(self.block_id.toPlainText()) or self.close())
|
|
||||||
self.blocked_users_label = QtGui.QLabel(self)
|
|
||||||
self.blocked_users_label.setGeometry(QtCore.QRect(10, 430, 330, 30))
|
|
||||||
self.comboBox = QtGui.QComboBox(self)
|
|
||||||
self.comboBox.setGeometry(QtCore.QRect(10, 460, 330, 30))
|
|
||||||
self.comboBox.addItems(settings['blocked'])
|
|
||||||
self.unblock = QtGui.QPushButton(self)
|
|
||||||
self.unblock.setGeometry(QtCore.QRect(10, 500, 330, 30))
|
|
||||||
self.unblock.clicked.connect(lambda: self.unblock_user())
|
|
||||||
self.retranslateUi()
|
|
||||||
QtCore.QMetaObject.connectSlotsByName(self)
|
|
||||||
|
|
||||||
def retranslateUi(self):
|
|
||||||
self.setWindowTitle(QtGui.QApplication.translate("privacySettings", "Privacy settings", None, QtGui.QApplication.UnicodeUTF8))
|
|
||||||
self.saveHistory.setText(QtGui.QApplication.translate("privacySettings", "Save chat history", None, QtGui.QApplication.UnicodeUTF8))
|
|
||||||
self.fileautoaccept.setText(QtGui.QApplication.translate("privacySettings", "Allow file auto accept", None, QtGui.QApplication.UnicodeUTF8))
|
|
||||||
self.typingNotifications.setText(QtGui.QApplication.translate("privacySettings", "Send typing notifications", None, QtGui.QApplication.UnicodeUTF8))
|
|
||||||
self.auto_path.setText(QtGui.QApplication.translate("privacySettings", "Auto accept default path:", None, QtGui.QApplication.UnicodeUTF8))
|
|
||||||
self.change_path.setText(QtGui.QApplication.translate("privacySettings", "Change", None, QtGui.QApplication.UnicodeUTF8))
|
|
||||||
self.inlines.setText(QtGui.QApplication.translate("privacySettings", "Allow inlines", None, QtGui.QApplication.UnicodeUTF8))
|
|
||||||
self.block_user_label.setText(QtGui.QApplication.translate("privacySettings", "Block by TOX ID:", None, QtGui.QApplication.UnicodeUTF8))
|
|
||||||
self.blocked_users_label.setText(QtGui.QApplication.translate("privacySettings", "Blocked users:", None, QtGui.QApplication.UnicodeUTF8))
|
|
||||||
self.unblock.setText(QtGui.QApplication.translate("privacySettings", "Unblock", None, QtGui.QApplication.UnicodeUTF8))
|
|
||||||
self.block.setText(QtGui.QApplication.translate("privacySettings", "Block user", None, QtGui.QApplication.UnicodeUTF8))
|
|
||||||
|
|
||||||
def unblock_user(self):
|
|
||||||
if not self.comboBox.count():
|
|
||||||
return
|
|
||||||
title = QtGui.QApplication.translate("privacySettings", "Add to friend list", None, QtGui.QApplication.UnicodeUTF8)
|
|
||||||
info = QtGui.QApplication.translate("privacySettings", "Do you want to add this user to friend list?", None, QtGui.QApplication.UnicodeUTF8)
|
|
||||||
reply = QtGui.QMessageBox.question(None, title, info, QtGui.QMessageBox.Yes, QtGui.QMessageBox.No)
|
|
||||||
Profile.get_instance().unblock_user(self.comboBox.currentText(), reply == QtGui.QMessageBox.Yes)
|
|
||||||
self.close()
|
|
||||||
|
|
||||||
def closeEvent(self, event):
|
|
||||||
settings = Settings.get_instance()
|
|
||||||
settings['typing_notifications'] = self.typingNotifications.isChecked()
|
|
||||||
settings['allow_auto_accept'] = self.fileautoaccept.isChecked()
|
|
||||||
if settings['save_history'] and not self.saveHistory.isChecked(): # clear history
|
|
||||||
reply = QtGui.QMessageBox.question(None,
|
|
||||||
QtGui.QApplication.translate("privacySettings",
|
|
||||||
'Chat history',
|
|
||||||
None, QtGui.QApplication.UnicodeUTF8),
|
|
||||||
QtGui.QApplication.translate("privacySettings",
|
|
||||||
'History will be cleaned! Continue?',
|
|
||||||
None, QtGui.QApplication.UnicodeUTF8),
|
|
||||||
QtGui.QMessageBox.Yes,
|
|
||||||
QtGui.QMessageBox.No)
|
|
||||||
if reply == QtGui.QMessageBox.Yes:
|
|
||||||
Profile.get_instance().clear_history()
|
|
||||||
settings['save_history'] = self.saveHistory.isChecked()
|
|
||||||
else:
|
|
||||||
settings['save_history'] = self.saveHistory.isChecked()
|
|
||||||
settings['auto_accept_path'] = self.path.toPlainText()
|
|
||||||
settings['allow_inline'] = self.inlines.isChecked()
|
|
||||||
settings.save()
|
|
||||||
|
|
||||||
def new_path(self):
|
|
||||||
directory = QtGui.QFileDialog.getExistingDirectory() + '/'
|
|
||||||
if directory != '/':
|
|
||||||
self.path.setPlainText(directory)
|
|
||||||
|
|
||||||
|
|
||||||
class NotificationsSettings(CenteredWidget):
|
|
||||||
"""Notifications settings form"""
|
|
||||||
|
|
||||||
def __init__(self):
|
|
||||||
super(NotificationsSettings, self).__init__()
|
|
||||||
self.initUI()
|
|
||||||
self.center()
|
|
||||||
|
|
||||||
def initUI(self):
|
|
||||||
self.setObjectName("notificationsForm")
|
|
||||||
self.resize(350, 200)
|
|
||||||
self.setMinimumSize(QtCore.QSize(350, 200))
|
|
||||||
self.setMaximumSize(QtCore.QSize(350, 200))
|
|
||||||
self.enableNotifications = QtGui.QCheckBox(self)
|
|
||||||
self.enableNotifications.setGeometry(QtCore.QRect(10, 20, 340, 18))
|
|
||||||
self.callsSound = QtGui.QCheckBox(self)
|
|
||||||
self.callsSound.setGeometry(QtCore.QRect(10, 120, 340, 18))
|
|
||||||
self.soundNotifications = QtGui.QCheckBox(self)
|
|
||||||
self.soundNotifications.setGeometry(QtCore.QRect(10, 70, 340, 18))
|
|
||||||
font = QtGui.QFont()
|
|
||||||
font.setPointSize(12)
|
|
||||||
font.setBold(True)
|
|
||||||
self.callsSound.setFont(font)
|
|
||||||
self.soundNotifications.setFont(font)
|
|
||||||
self.enableNotifications.setFont(font)
|
|
||||||
s = Settings.get_instance()
|
|
||||||
self.enableNotifications.setChecked(s['notifications'])
|
|
||||||
self.soundNotifications.setChecked(s['sound_notifications'])
|
|
||||||
self.callsSound.setChecked(s['calls_sound'])
|
|
||||||
self.retranslateUi()
|
|
||||||
QtCore.QMetaObject.connectSlotsByName(self)
|
|
||||||
|
|
||||||
def retranslateUi(self):
|
|
||||||
self.setWindowTitle(QtGui.QApplication.translate("notificationsForm", "Notification settings", None, QtGui.QApplication.UnicodeUTF8))
|
|
||||||
self.enableNotifications.setText(QtGui.QApplication.translate("notificationsForm", "Enable notifications", None, QtGui.QApplication.UnicodeUTF8))
|
|
||||||
self.callsSound.setText(QtGui.QApplication.translate("notificationsForm", "Enable call\'s sound", None, QtGui.QApplication.UnicodeUTF8))
|
|
||||||
self.soundNotifications.setText(QtGui.QApplication.translate("notificationsForm", "Enable sound notifications", None, QtGui.QApplication.UnicodeUTF8))
|
|
||||||
|
|
||||||
def closeEvent(self, *args, **kwargs):
|
|
||||||
settings = Settings.get_instance()
|
|
||||||
settings['notifications'] = self.enableNotifications.isChecked()
|
|
||||||
settings['sound_notifications'] = self.soundNotifications.isChecked()
|
|
||||||
settings['calls_sound'] = self.callsSound.isChecked()
|
|
||||||
settings.save()
|
|
||||||
|
|
||||||
|
|
||||||
class InterfaceSettings(CenteredWidget):
|
|
||||||
"""Interface settings form"""
|
|
||||||
|
|
||||||
def __init__(self):
|
|
||||||
super(InterfaceSettings, self).__init__()
|
|
||||||
self.initUI()
|
|
||||||
self.center()
|
|
||||||
|
|
||||||
def initUI(self):
|
|
||||||
self.setObjectName("interfaceForm")
|
|
||||||
self.resize(300, 300)
|
|
||||||
self.setMinimumSize(QtCore.QSize(300, 300))
|
|
||||||
self.setMaximumSize(QtCore.QSize(300, 300))
|
|
||||||
self.setBaseSize(QtCore.QSize(300, 300))
|
|
||||||
self.label = QtGui.QLabel(self)
|
|
||||||
self.label.setGeometry(QtCore.QRect(30, 20, 91, 21))
|
|
||||||
font = QtGui.QFont()
|
|
||||||
font.setPointSize(16)
|
|
||||||
font.setWeight(75)
|
|
||||||
font.setBold(True)
|
|
||||||
self.label.setFont(font)
|
|
||||||
self.label.setObjectName("label")
|
|
||||||
self.themeSelect = QtGui.QComboBox(self)
|
|
||||||
self.themeSelect.setGeometry(QtCore.QRect(30, 60, 160, 30))
|
|
||||||
self.themeSelect.setObjectName("themeSelect")
|
|
||||||
list_of_themes = ['default', 'windows', 'gtk', 'cde', 'plastique', 'motif']
|
|
||||||
self.themeSelect.addItems(list_of_themes)
|
|
||||||
settings = Settings.get_instance()
|
|
||||||
theme = settings['theme']
|
|
||||||
index = list_of_themes.index(theme)
|
|
||||||
self.themeSelect.setCurrentIndex(index)
|
|
||||||
self.lang_choose = QtGui.QComboBox(self)
|
|
||||||
self.lang_choose.setGeometry(QtCore.QRect(30, 150, 160, 30))
|
|
||||||
self.lang_choose.setObjectName("comboBox")
|
|
||||||
supported = Settings.supported_languages()
|
|
||||||
for elem in supported:
|
|
||||||
self.lang_choose.addItem(elem[0])
|
|
||||||
lang = settings['language']
|
|
||||||
index = map(lambda x: x[0], supported).index(lang)
|
|
||||||
self.lang_choose.setCurrentIndex(index)
|
|
||||||
self.lang = QtGui.QLabel(self)
|
|
||||||
self.lang.setGeometry(QtCore.QRect(30, 110, 121, 31))
|
|
||||||
self.lang.setFont(font)
|
|
||||||
self.retranslateUi()
|
|
||||||
QtCore.QMetaObject.connectSlotsByName(self)
|
|
||||||
|
|
||||||
def retranslateUi(self):
|
|
||||||
self.setWindowTitle(QtGui.QApplication.translate("interfaceForm", "Interface settings", None, QtGui.QApplication.UnicodeUTF8))
|
|
||||||
self.label.setText(QtGui.QApplication.translate("interfaceForm", "Theme:", None, QtGui.QApplication.UnicodeUTF8))
|
|
||||||
self.lang.setText(QtGui.QApplication.translate("interfaceForm", "Language:", None, QtGui.QApplication.UnicodeUTF8))
|
|
||||||
|
|
||||||
def closeEvent(self, event):
|
|
||||||
settings = Settings.get_instance()
|
|
||||||
style = str(self.themeSelect.currentText())
|
|
||||||
settings['theme'] = style
|
|
||||||
QtGui.QApplication.setStyle(get_style(style))
|
|
||||||
language = self.lang_choose.currentText()
|
|
||||||
if settings['language'] != language:
|
|
||||||
settings['language'] = language
|
|
||||||
index = self.lang_choose.currentIndex()
|
|
||||||
path = Settings.supported_languages()[index][1]
|
|
||||||
app = QtGui.QApplication.instance()
|
|
||||||
app.removeTranslator(app.translator)
|
|
||||||
app.translator.load(curr_directory() + '/translations/' + path)
|
|
||||||
app.installTranslator(app.translator)
|
|
||||||
settings.save()
|
|
||||||
|
|
||||||
|
|
||||||
class AudioSettings(CenteredWidget):
|
|
||||||
|
|
||||||
def __init__(self):
|
|
||||||
super(AudioSettings, self).__init__()
|
|
||||||
self.initUI()
|
|
||||||
self.retranslateUi()
|
|
||||||
self.center()
|
|
||||||
|
|
||||||
def initUI(self):
|
|
||||||
self.setObjectName("audioSettingsForm")
|
|
||||||
self.resize(400, 150)
|
|
||||||
self.setMinimumSize(QtCore.QSize(400, 150))
|
|
||||||
self.setMaximumSize(QtCore.QSize(400, 150))
|
|
||||||
self.in_label = QtGui.QLabel(self)
|
|
||||||
self.in_label.setGeometry(QtCore.QRect(25, 5, 350, 20))
|
|
||||||
self.out_label = QtGui.QLabel(self)
|
|
||||||
self.out_label.setGeometry(QtCore.QRect(25, 65, 350, 20))
|
|
||||||
font = QtGui.QFont()
|
|
||||||
font.setPointSize(16)
|
|
||||||
font.setBold(True)
|
|
||||||
self.in_label.setFont(font)
|
|
||||||
self.out_label.setFont(font)
|
|
||||||
self.input = QtGui.QComboBox(self)
|
|
||||||
self.input.setGeometry(QtCore.QRect(25, 30, 350, 30))
|
|
||||||
self.output = QtGui.QComboBox(self)
|
|
||||||
self.output.setGeometry(QtCore.QRect(25, 90, 350, 30))
|
|
||||||
p = pyaudio.PyAudio()
|
|
||||||
settings = Settings.get_instance()
|
|
||||||
self.in_indexes, self.out_indexes = [], []
|
|
||||||
for i in xrange(p.get_device_count()):
|
|
||||||
device = p.get_device_info_by_index(i)
|
|
||||||
if device["maxInputChannels"]:
|
|
||||||
self.input.addItem(unicode(device["name"]))
|
|
||||||
self.in_indexes.append(i)
|
|
||||||
if device["maxOutputChannels"]:
|
|
||||||
self.output.addItem(unicode(device["name"]))
|
|
||||||
self.out_indexes.append(i)
|
|
||||||
self.input.setCurrentIndex(self.in_indexes.index(settings.audio['input']))
|
|
||||||
self.output.setCurrentIndex(self.out_indexes.index(settings.audio['output']))
|
|
||||||
QtCore.QMetaObject.connectSlotsByName(self)
|
|
||||||
|
|
||||||
def retranslateUi(self):
|
|
||||||
self.setWindowTitle(QtGui.QApplication.translate("audioSettingsForm", "Audio settings", None, QtGui.QApplication.UnicodeUTF8))
|
|
||||||
self.in_label.setText(QtGui.QApplication.translate("audioSettingsForm", "Input device:", None, QtGui.QApplication.UnicodeUTF8))
|
|
||||||
self.out_label.setText(QtGui.QApplication.translate("audioSettingsForm", "Output device:", None, QtGui.QApplication.UnicodeUTF8))
|
|
||||||
|
|
||||||
def closeEvent(self, event):
|
|
||||||
settings = Settings.get_instance()
|
|
||||||
settings.audio['input'] = self.in_indexes[self.input.currentIndex()]
|
|
||||||
settings.audio['output'] = self.out_indexes[self.output.currentIndex()]
|
|
||||||
settings.save()
|
|
||||||
|
|
||||||
|
|
||||||
class PluginsSettings(CenteredWidget):
|
|
||||||
|
|
||||||
def __init__(self):
|
|
||||||
super(PluginsSettings, self).__init__()
|
|
||||||
self.initUI()
|
|
||||||
self.center()
|
|
||||||
self.retranslateUi()
|
|
||||||
|
|
||||||
def initUI(self):
|
|
||||||
self.resize(400, 210)
|
|
||||||
self.setMinimumSize(QtCore.QSize(400, 210))
|
|
||||||
self.setMaximumSize(QtCore.QSize(400, 210))
|
|
||||||
self.comboBox = QtGui.QComboBox(self)
|
|
||||||
self.comboBox.setGeometry(QtCore.QRect(30, 10, 340, 30))
|
|
||||||
self.label = QtGui.QLabel(self)
|
|
||||||
self.label.setGeometry(QtCore.QRect(30, 40, 340, 90))
|
|
||||||
self.label.setWordWrap(True)
|
|
||||||
self.button = QtGui.QPushButton(self)
|
|
||||||
self.button.setGeometry(QtCore.QRect(30, 130, 340, 30))
|
|
||||||
self.button.clicked.connect(self.button_click)
|
|
||||||
self.open = QtGui.QPushButton(self)
|
|
||||||
self.open.setGeometry(QtCore.QRect(30, 170, 340, 30))
|
|
||||||
self.open.clicked.connect(self.open_plugin)
|
|
||||||
self.pl_loader = plugin_support.PluginLoader.get_instance()
|
|
||||||
self.update_list()
|
|
||||||
self.comboBox.currentIndexChanged.connect(self.show_data)
|
|
||||||
self.show_data()
|
|
||||||
|
|
||||||
def retranslateUi(self):
|
|
||||||
self.setWindowTitle(QtGui.QApplication.translate('PluginsForm', "Plugins", None, QtGui.QApplication.UnicodeUTF8))
|
|
||||||
self.open.setText(QtGui.QApplication.translate('PluginsForm', "Open selected plugin", None, QtGui.QApplication.UnicodeUTF8))
|
|
||||||
|
|
||||||
def open_plugin(self):
|
|
||||||
ind = self.comboBox.currentIndex()
|
|
||||||
plugin = self.data[ind]
|
|
||||||
window = self.pl_loader.plugin_window(plugin[-1])
|
|
||||||
if window is not None:
|
|
||||||
self.window = window
|
|
||||||
self.window.show()
|
|
||||||
else:
|
|
||||||
msgBox = QtGui.QMessageBox()
|
|
||||||
text = (QtGui.QApplication.translate("PluginsForm", 'No GUI found for this plugin', None,
|
|
||||||
QtGui.QApplication.UnicodeUTF8))
|
|
||||||
msgBox.setText(text)
|
|
||||||
msgBox.exec_()
|
|
||||||
|
|
||||||
def update_list(self):
|
|
||||||
self.comboBox.clear()
|
|
||||||
data = self.pl_loader.get_plugins_list()
|
|
||||||
self.comboBox.addItems(map(lambda x: x[0], data))
|
|
||||||
self.data = data
|
|
||||||
|
|
||||||
def show_data(self):
|
|
||||||
ind = self.comboBox.currentIndex()
|
|
||||||
plugin = self.data[ind]
|
|
||||||
descr = plugin[2] or QtGui.QApplication.translate("PluginsForm", "No description available", None, QtGui.QApplication.UnicodeUTF8)
|
|
||||||
self.label.setText(descr)
|
|
||||||
if plugin[1]:
|
|
||||||
self.button.setText(QtGui.QApplication.translate("PluginsForm", "Disable plugin", None, QtGui.QApplication.UnicodeUTF8))
|
|
||||||
else:
|
|
||||||
self.button.setText(QtGui.QApplication.translate("PluginsForm", "Enable plugin", None, QtGui.QApplication.UnicodeUTF8))
|
|
||||||
|
|
||||||
def button_click(self):
|
|
||||||
ind = self.comboBox.currentIndex()
|
|
||||||
plugin = self.data[ind]
|
|
||||||
self.pl_loader.toggle_plugin(plugin[-1])
|
|
||||||
plugin[1] = not plugin[1]
|
|
||||||
if plugin[1]:
|
|
||||||
self.button.setText(QtGui.QApplication.translate("PluginsForm", "Disable plugin", None, QtGui.QApplication.UnicodeUTF8))
|
|
||||||
else:
|
|
||||||
self.button.setText(QtGui.QApplication.translate("PluginsForm", "Enable plugin", None, QtGui.QApplication.UnicodeUTF8))
|
|
@ -1,89 +0,0 @@
|
|||||||
|
|
||||||
|
|
||||||
MESSAGE_TYPE = {
|
|
||||||
'TEXT': 0,
|
|
||||||
'ACTION': 1,
|
|
||||||
'FILE_TRANSFER': 2,
|
|
||||||
'INLINE': 3
|
|
||||||
}
|
|
||||||
|
|
||||||
FILE_TRANSFER_MESSAGE_STATUS = {
|
|
||||||
'FINISHED': 0,
|
|
||||||
'CANCELLED': 1,
|
|
||||||
'OUTGOING': 2,
|
|
||||||
'INCOMING_NOT_STARTED': 3,
|
|
||||||
'INCOMING_STARTED': 4,
|
|
||||||
'PAUSED_BY_FRIEND': 5,
|
|
||||||
'PAUSED_BY_USER': 6
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
class Message(object):
|
|
||||||
|
|
||||||
def __init__(self, message_type, owner, time):
|
|
||||||
self._time = time
|
|
||||||
self._type = message_type
|
|
||||||
self._owner = owner
|
|
||||||
|
|
||||||
def get_type(self):
|
|
||||||
return self._type
|
|
||||||
|
|
||||||
def get_owner(self):
|
|
||||||
return self._owner
|
|
||||||
|
|
||||||
|
|
||||||
class TextMessage(Message):
|
|
||||||
"""
|
|
||||||
Plain text or action message
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self, message, owner, time, message_type):
|
|
||||||
super(TextMessage, self).__init__(message_type, owner, time)
|
|
||||||
self._message = message
|
|
||||||
|
|
||||||
def get_data(self):
|
|
||||||
return self._message, self._owner, self._time, self._type
|
|
||||||
|
|
||||||
|
|
||||||
class TransferMessage(Message):
|
|
||||||
"""
|
|
||||||
Message with info about file transfer
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self, owner, time, status, size, name, friend_number, file_number):
|
|
||||||
super(TransferMessage, self).__init__(MESSAGE_TYPE['FILE_TRANSFER'], owner, time)
|
|
||||||
self._status = status
|
|
||||||
self._size = size
|
|
||||||
self._file_name = name
|
|
||||||
self._friend_number, self._file_number = friend_number, file_number
|
|
||||||
|
|
||||||
def is_active(self, file_number):
|
|
||||||
return self._file_number == file_number and self._status > 1
|
|
||||||
|
|
||||||
def get_friend_number(self):
|
|
||||||
return self._friend_number
|
|
||||||
|
|
||||||
def get_file_number(self):
|
|
||||||
return self._file_number
|
|
||||||
|
|
||||||
def get_status(self):
|
|
||||||
return self._status
|
|
||||||
|
|
||||||
def set_status(self, value):
|
|
||||||
self._status = value
|
|
||||||
|
|
||||||
def get_data(self):
|
|
||||||
return self._file_name, self._size, self._time, self._owner, self._friend_number, self._file_number, self._status
|
|
||||||
|
|
||||||
|
|
||||||
class InlineImage(Message):
|
|
||||||
"""
|
|
||||||
Inline image
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self, data):
|
|
||||||
super(InlineImage, self).__init__(MESSAGE_TYPE['INLINE'], None, None)
|
|
||||||
self._data = data
|
|
||||||
|
|
||||||
def get_data(self):
|
|
||||||
return self._data
|
|
@ -1,75 +0,0 @@
|
|||||||
try:
|
|
||||||
from PySide import QtCore, QtGui
|
|
||||||
except ImportError:
|
|
||||||
from PyQt4 import QtCore, QtGui
|
|
||||||
from util import curr_directory
|
|
||||||
import wave
|
|
||||||
import pyaudio
|
|
||||||
|
|
||||||
|
|
||||||
SOUND_NOTIFICATION = {
|
|
||||||
'MESSAGE': 0,
|
|
||||||
'FRIEND_CONNECTION_STATUS': 1,
|
|
||||||
'FILE_TRANSFER': 2
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
def tray_notification(title, text, tray, window):
|
|
||||||
"""
|
|
||||||
Show tray notification and activate window icon
|
|
||||||
NOTE: different behaviour on different OS
|
|
||||||
:param title: Name of user who sent message or file
|
|
||||||
:param text: text of message or file info
|
|
||||||
:param tray: ref to tray icon
|
|
||||||
:param window: main window
|
|
||||||
"""
|
|
||||||
if QtGui.QSystemTrayIcon.isSystemTrayAvailable():
|
|
||||||
if len(text) > 30:
|
|
||||||
text = text[:27] + '...'
|
|
||||||
tray.showMessage(title, text, QtGui.QSystemTrayIcon.NoIcon, 3000)
|
|
||||||
QtGui.QApplication.alert(window, 0)
|
|
||||||
|
|
||||||
def message_clicked():
|
|
||||||
window.setWindowState(window.windowState() & ~QtCore.Qt.WindowMinimized | QtCore.Qt.WindowActive)
|
|
||||||
window.activateWindow()
|
|
||||||
tray.connect(tray, QtCore.SIGNAL("messageClicked()"), message_clicked)
|
|
||||||
|
|
||||||
|
|
||||||
class AudioFile(object):
|
|
||||||
chunk = 1024
|
|
||||||
|
|
||||||
def __init__(self, fl):
|
|
||||||
self.wf = wave.open(fl, 'rb')
|
|
||||||
self.p = pyaudio.PyAudio()
|
|
||||||
self.stream = self.p.open(
|
|
||||||
format=self.p.get_format_from_width(self.wf.getsampwidth()),
|
|
||||||
channels=self.wf.getnchannels(),
|
|
||||||
rate=self.wf.getframerate(),
|
|
||||||
output=True
|
|
||||||
)
|
|
||||||
|
|
||||||
def play(self):
|
|
||||||
data = self.wf.readframes(self.chunk)
|
|
||||||
while data:
|
|
||||||
self.stream.write(data)
|
|
||||||
data = self.wf.readframes(self.chunk)
|
|
||||||
|
|
||||||
def close(self):
|
|
||||||
self.stream.close()
|
|
||||||
self.p.terminate()
|
|
||||||
|
|
||||||
|
|
||||||
def sound_notification(t):
|
|
||||||
"""
|
|
||||||
Plays sound notification
|
|
||||||
:param t: type of notification
|
|
||||||
"""
|
|
||||||
if t == SOUND_NOTIFICATION['MESSAGE']:
|
|
||||||
f = curr_directory() + '/sounds/message.wav'
|
|
||||||
elif t == SOUND_NOTIFICATION['FILE_TRANSFER']:
|
|
||||||
f = curr_directory() + '/sounds/file.wav'
|
|
||||||
else:
|
|
||||||
f = curr_directory() + '/sounds/contact.wav'
|
|
||||||
a = AudioFile(f)
|
|
||||||
a.play()
|
|
||||||
a.close()
|
|
@ -1,76 +0,0 @@
|
|||||||
from widgets import CenteredWidget
|
|
||||||
try:
|
|
||||||
from PySide import QtCore, QtGui
|
|
||||||
except ImportError:
|
|
||||||
from PyQt4 import QtCore, QtGui
|
|
||||||
|
|
||||||
|
|
||||||
class PasswordArea(QtGui.QLineEdit):
|
|
||||||
|
|
||||||
def __init__(self, parent):
|
|
||||||
super(PasswordArea, self).__init__(parent)
|
|
||||||
self.parent = parent
|
|
||||||
self.setEchoMode(QtGui.QLineEdit.EchoMode.Password)
|
|
||||||
|
|
||||||
def keyPressEvent(self, event):
|
|
||||||
if event.key() == QtCore.Qt.Key_Return:
|
|
||||||
self.parent.button_click()
|
|
||||||
else:
|
|
||||||
super(PasswordArea, self).keyPressEvent(event)
|
|
||||||
|
|
||||||
|
|
||||||
class PasswordScreen(CenteredWidget):
|
|
||||||
|
|
||||||
def __init__(self, encrypt, data):
|
|
||||||
super(PasswordScreen, self).__init__()
|
|
||||||
self._encrypt = encrypt
|
|
||||||
self._data = data
|
|
||||||
self.initUI()
|
|
||||||
|
|
||||||
def initUI(self):
|
|
||||||
self.resize(360, 170)
|
|
||||||
self.setMinimumSize(QtCore.QSize(360, 170))
|
|
||||||
self.setMaximumSize(QtCore.QSize(360, 170))
|
|
||||||
|
|
||||||
self.enter_pass = QtGui.QLabel(self)
|
|
||||||
self.enter_pass.setGeometry(QtCore.QRect(30, 10, 300, 30))
|
|
||||||
|
|
||||||
self.password = PasswordArea(self)
|
|
||||||
self.password.setGeometry(QtCore.QRect(30, 50, 300, 30))
|
|
||||||
|
|
||||||
self.button = QtGui.QPushButton(self)
|
|
||||||
self.button.setGeometry(QtCore.QRect(30, 90, 300, 30))
|
|
||||||
self.button.setText('OK')
|
|
||||||
self.button.clicked.connect(self.button_click)
|
|
||||||
|
|
||||||
self.warning = QtGui.QLabel(self)
|
|
||||||
self.warning.setGeometry(QtCore.QRect(30, 130, 300, 30))
|
|
||||||
self.warning.setStyleSheet('QLabel { color: #F70D1A; }')
|
|
||||||
self.warning.setVisible(False)
|
|
||||||
|
|
||||||
self.retranslateUi()
|
|
||||||
self.center()
|
|
||||||
QtCore.QMetaObject.connectSlotsByName(self)
|
|
||||||
|
|
||||||
def button_click(self):
|
|
||||||
if self.password.text():
|
|
||||||
try:
|
|
||||||
self._encrypt.set_password(self.password.text())
|
|
||||||
new_data = self._encrypt.pass_decrypt(self._data[0])
|
|
||||||
except Exception:
|
|
||||||
self.warning.setVisible(True)
|
|
||||||
else:
|
|
||||||
self._data[0] = new_data
|
|
||||||
self.close()
|
|
||||||
|
|
||||||
def keyPressEvent(self, event):
|
|
||||||
if event.key() == QtCore.Qt.Key_Enter:
|
|
||||||
self.button_click()
|
|
||||||
else:
|
|
||||||
super(PasswordScreen, self).keyPressEvent(event)
|
|
||||||
|
|
||||||
def retranslateUi(self):
|
|
||||||
self.setWindowTitle(QtGui.QApplication.translate("pass", "Enter password", None, QtGui.QApplication.UnicodeUTF8))
|
|
||||||
self.enter_pass.setText(QtGui.QApplication.translate("pass", "Password:", None, QtGui.QApplication.UnicodeUTF8))
|
|
||||||
self.warning.setText(QtGui.QApplication.translate("pass", "Incorrect password", None, QtGui.QApplication.UnicodeUTF8))
|
|
||||||
|
|
@ -1,146 +0,0 @@
|
|||||||
import util
|
|
||||||
import profile
|
|
||||||
import os
|
|
||||||
import imp
|
|
||||||
import inspect
|
|
||||||
import plugins.plugin_super_class as pl
|
|
||||||
import toxencryptsave
|
|
||||||
|
|
||||||
|
|
||||||
class PluginLoader(util.Singleton):
|
|
||||||
|
|
||||||
def __init__(self, tox, settings):
|
|
||||||
self._profile = profile.Profile.get_instance()
|
|
||||||
self._settings = settings
|
|
||||||
self._plugins = {} # dict. key - plugin unique short name, value - tuple (plugin instance, is active)
|
|
||||||
self._tox = tox
|
|
||||||
self._encr = toxencryptsave.LibToxEncryptSave.get_instance()
|
|
||||||
|
|
||||||
def set_tox(self, tox):
|
|
||||||
"""
|
|
||||||
New tox instance
|
|
||||||
"""
|
|
||||||
self._tox = tox
|
|
||||||
for value in self._plugins.values():
|
|
||||||
value[0].set_tox(tox)
|
|
||||||
|
|
||||||
def load(self):
|
|
||||||
"""
|
|
||||||
Load all plugins in plugins folder
|
|
||||||
"""
|
|
||||||
path = util.curr_directory() + '/plugins/'
|
|
||||||
files = [f for f in os.listdir(path) if os.path.isfile(os.path.join(path, f))]
|
|
||||||
for fl in files:
|
|
||||||
if fl in ('plugin_super_class.py', '__init__.py') or not fl.endswith('.py'):
|
|
||||||
continue
|
|
||||||
name = fl[:-3] # module name without .py
|
|
||||||
try:
|
|
||||||
module = imp.load_source('plugins.' + name, path + fl) # import plugin
|
|
||||||
except ImportError:
|
|
||||||
util.log('Import error in module ' + name)
|
|
||||||
continue
|
|
||||||
except Exception as ex:
|
|
||||||
util.log('Exception in module ' + name + ' Exception: ' + str(ex))
|
|
||||||
continue
|
|
||||||
for elem in dir(module):
|
|
||||||
obj = getattr(module, elem)
|
|
||||||
if inspect.isclass(obj) and issubclass(obj, pl.PluginSuperClass): # looking for plugin class in module
|
|
||||||
print 'Plugin', elem
|
|
||||||
try: # create instance of plugin class
|
|
||||||
inst = obj(self._tox, self._profile, self._settings, self._encr)
|
|
||||||
autostart = inst.get_short_name() in self._settings['plugins']
|
|
||||||
if autostart:
|
|
||||||
inst.start()
|
|
||||||
except Exception as ex:
|
|
||||||
util.log('Exception in module ' + name + ' Exception: ' + str(ex))
|
|
||||||
continue
|
|
||||||
self._plugins[inst.get_short_name()] = [inst, autostart] # (inst, is active)
|
|
||||||
break
|
|
||||||
|
|
||||||
def callback_lossless(self, friend_number, data, length):
|
|
||||||
"""
|
|
||||||
New incoming custom lossless packet (callback)
|
|
||||||
"""
|
|
||||||
l = data[0] - pl.LOSSLESS_FIRST_BYTE
|
|
||||||
name = ''.join(chr(x) for x in data[1:l + 1])
|
|
||||||
if name in self._plugins and self._plugins[name][1]:
|
|
||||||
self._plugins[name][0].lossless_packet(''.join(chr(x) for x in data[l + 1:length]), friend_number)
|
|
||||||
|
|
||||||
def callback_lossy(self, friend_number, data, length):
|
|
||||||
"""
|
|
||||||
New incoming custom lossy packet (callback)
|
|
||||||
"""
|
|
||||||
l = data[0] - pl.LOSSY_FIRST_BYTE
|
|
||||||
name = ''.join(chr(x) for x in data[1:l + 1])
|
|
||||||
if name in self._plugins and self._plugins[name][1]:
|
|
||||||
self._plugins[name][0].lossy_packet(''.join(chr(x) for x in data[l + 1:length]), friend_number)
|
|
||||||
|
|
||||||
def friend_online(self, friend_number):
|
|
||||||
for elem in self._plugins.values():
|
|
||||||
if elem[1]:
|
|
||||||
elem[0].friend_connected(friend_number)
|
|
||||||
|
|
||||||
def get_plugins_list(self):
|
|
||||||
"""
|
|
||||||
Returns list of all plugins
|
|
||||||
"""
|
|
||||||
result = []
|
|
||||||
for data in self._plugins.values():
|
|
||||||
result.append([data[0].get_name(), # plugin full name
|
|
||||||
data[1], # is enabled
|
|
||||||
data[0].get_description(), # plugin description
|
|
||||||
data[0].get_short_name()]) # key - short unique name
|
|
||||||
return result
|
|
||||||
|
|
||||||
def plugin_window(self, key):
|
|
||||||
"""
|
|
||||||
Return window or None for specified plugin
|
|
||||||
"""
|
|
||||||
return self._plugins[key][0].get_window()
|
|
||||||
|
|
||||||
def toggle_plugin(self, key):
|
|
||||||
"""
|
|
||||||
Enable/disable plugin
|
|
||||||
:param key: plugin short name
|
|
||||||
"""
|
|
||||||
plugin = self._plugins[key]
|
|
||||||
if plugin[1]:
|
|
||||||
plugin[0].stop()
|
|
||||||
else:
|
|
||||||
plugin[0].start()
|
|
||||||
plugin[1] = not plugin[1]
|
|
||||||
if plugin[1]:
|
|
||||||
self._settings['plugins'].append(key)
|
|
||||||
else:
|
|
||||||
self._settings['plugins'].remove(key)
|
|
||||||
self._settings.save()
|
|
||||||
|
|
||||||
def command(self, text):
|
|
||||||
"""
|
|
||||||
New command for plugin
|
|
||||||
"""
|
|
||||||
text = text.strip()
|
|
||||||
name = text.split()[0]
|
|
||||||
if name in self._plugins and self._plugins[name][1]:
|
|
||||||
self._plugins[name][0].command(text[len(name) + 1:])
|
|
||||||
|
|
||||||
def get_menu(self, menu, num):
|
|
||||||
"""
|
|
||||||
Return list of items for menu
|
|
||||||
"""
|
|
||||||
result = []
|
|
||||||
for elem in self._plugins.values():
|
|
||||||
if elem[1]:
|
|
||||||
try:
|
|
||||||
result.extend(elem[0].get_menu(menu, num))
|
|
||||||
except:
|
|
||||||
continue
|
|
||||||
return result
|
|
||||||
|
|
||||||
def stop(self):
|
|
||||||
"""
|
|
||||||
App is closing, stop all plugins
|
|
||||||
"""
|
|
||||||
for key in self._plugins.keys():
|
|
||||||
self._plugins[key][0].close()
|
|
||||||
del self._plugins[key]
|
|
1221
src/profile.py
232
src/settings.py
@ -1,232 +0,0 @@
|
|||||||
from platform import system
|
|
||||||
import json
|
|
||||||
import os
|
|
||||||
import locale
|
|
||||||
from util import Singleton, curr_directory, log
|
|
||||||
import pyaudio
|
|
||||||
from toxencryptsave import LibToxEncryptSave
|
|
||||||
|
|
||||||
|
|
||||||
class Settings(Singleton, dict):
|
|
||||||
"""
|
|
||||||
Settings of current profile + global app settings
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self, name):
|
|
||||||
self.path = ProfileHelper.get_path() + str(name) + '.json'
|
|
||||||
self.name = name
|
|
||||||
if os.path.isfile(self.path):
|
|
||||||
with open(self.path) as fl:
|
|
||||||
data = fl.read()
|
|
||||||
inst = LibToxEncryptSave.get_instance()
|
|
||||||
if inst.has_password():
|
|
||||||
data = inst.pass_decrypt(data)
|
|
||||||
try:
|
|
||||||
info = json.loads(data)
|
|
||||||
except Exception as ex:
|
|
||||||
info = Settings.get_default_settings()
|
|
||||||
log('Parsing settings error: ' + str(ex))
|
|
||||||
super(self.__class__, self).__init__(info)
|
|
||||||
self.upgrade()
|
|
||||||
else:
|
|
||||||
super(self.__class__, self).__init__(Settings.get_default_settings())
|
|
||||||
self.save()
|
|
||||||
p = pyaudio.PyAudio()
|
|
||||||
self.audio = {'input': p.get_default_input_device_info()['index'],
|
|
||||||
'output': p.get_default_output_device_info()['index']}
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def get_auto_profile():
|
|
||||||
path = Settings.get_default_path() + 'toxygen.json'
|
|
||||||
if os.path.isfile(path):
|
|
||||||
with open(path) as fl:
|
|
||||||
data = fl.read()
|
|
||||||
auto = json.loads(data)
|
|
||||||
if 'path' in auto and 'name' in auto:
|
|
||||||
return unicode(auto['path']), unicode(auto['name'])
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def set_auto_profile(path, name):
|
|
||||||
p = Settings.get_default_path() + 'toxygen.json'
|
|
||||||
data = json.dumps({'path': unicode(path.decode(locale.getpreferredencoding())), 'name': unicode(name)})
|
|
||||||
with open(p, 'w') as fl:
|
|
||||||
fl.write(data)
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def get_default_settings():
|
|
||||||
"""
|
|
||||||
Default profile settings
|
|
||||||
"""
|
|
||||||
return {
|
|
||||||
'theme': 'default',
|
|
||||||
'ipv6_enabled': True,
|
|
||||||
'udp_enabled': True,
|
|
||||||
'proxy_type': 0,
|
|
||||||
'proxy_host': '0',
|
|
||||||
'proxy_port': 0,
|
|
||||||
'start_port': 0,
|
|
||||||
'end_port': 0,
|
|
||||||
'tcp_port': 0,
|
|
||||||
'notifications': True,
|
|
||||||
'sound_notifications': False,
|
|
||||||
'language': 'English',
|
|
||||||
'save_history': False,
|
|
||||||
'allow_inline': True,
|
|
||||||
'allow_auto_accept': True,
|
|
||||||
'auto_accept_path': None,
|
|
||||||
'show_online_friends': False,
|
|
||||||
'auto_accept_from_friends': [],
|
|
||||||
'friends_aliases': [],
|
|
||||||
'typing_notifications': False,
|
|
||||||
'calls_sound': True,
|
|
||||||
'blocked': [],
|
|
||||||
'plugins': []
|
|
||||||
}
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def supported_languages():
|
|
||||||
return [
|
|
||||||
('English', 'en_EN'),
|
|
||||||
('Russian', 'ru_RU'),
|
|
||||||
('French', 'fr_FR')
|
|
||||||
]
|
|
||||||
|
|
||||||
def upgrade(self):
|
|
||||||
default = Settings.get_default_settings()
|
|
||||||
for key in default:
|
|
||||||
if key not in self:
|
|
||||||
print key
|
|
||||||
self[key] = default[key]
|
|
||||||
self.save()
|
|
||||||
|
|
||||||
def save(self):
|
|
||||||
text = json.dumps(self)
|
|
||||||
inst = LibToxEncryptSave.get_instance()
|
|
||||||
if inst.has_password():
|
|
||||||
text = inst.pass_encrypt(text)
|
|
||||||
with open(self.path, 'w') as fl:
|
|
||||||
fl.write(text)
|
|
||||||
|
|
||||||
def close(self):
|
|
||||||
path = Settings.get_default_path() + 'toxygen.json'
|
|
||||||
if os.path.isfile(path):
|
|
||||||
with open(path) as fl:
|
|
||||||
data = fl.read()
|
|
||||||
app_settings = json.loads(data)
|
|
||||||
try:
|
|
||||||
app_settings['active_profile'].remove(unicode(ProfileHelper.get_path() + self.name + '.tox'))
|
|
||||||
except:
|
|
||||||
pass
|
|
||||||
data = json.dumps(app_settings)
|
|
||||||
with open(path, 'w') as fl:
|
|
||||||
fl.write(data)
|
|
||||||
|
|
||||||
def set_active_profile(self):
|
|
||||||
"""
|
|
||||||
Mark current profile as active
|
|
||||||
"""
|
|
||||||
path = Settings.get_default_path() + 'toxygen.json'
|
|
||||||
if os.path.isfile(path):
|
|
||||||
with open(path) as fl:
|
|
||||||
data = fl.read()
|
|
||||||
app_settings = json.loads(data)
|
|
||||||
else:
|
|
||||||
app_settings = {}
|
|
||||||
if 'active_profile' not in app_settings:
|
|
||||||
app_settings['active_profile'] = []
|
|
||||||
profile_path = ProfileHelper.get_path()
|
|
||||||
app_settings['active_profile'].append(unicode(profile_path + str(self.name) + '.tox'))
|
|
||||||
data = json.dumps(app_settings)
|
|
||||||
with open(path, 'w') as fl:
|
|
||||||
fl.write(data)
|
|
||||||
|
|
||||||
def export(self, path):
|
|
||||||
text = json.dumps(self)
|
|
||||||
with open(path + str(self.name) + '.json', 'w') as fl:
|
|
||||||
fl.write(text)
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def get_default_path():
|
|
||||||
if system() == 'Linux':
|
|
||||||
return os.getenv('HOME') + '/.config/tox/'
|
|
||||||
elif system() == 'Windows':
|
|
||||||
return os.getenv('APPDATA') + '/Tox/'
|
|
||||||
|
|
||||||
|
|
||||||
class ProfileHelper(Singleton):
|
|
||||||
"""
|
|
||||||
Class with methods for search, load and save profiles
|
|
||||||
"""
|
|
||||||
def __init__(self, path, name):
|
|
||||||
path = path.decode(locale.getpreferredencoding())
|
|
||||||
self._path = path + name + '.tox'
|
|
||||||
self._directory = path
|
|
||||||
# create /avatars if not exists:
|
|
||||||
directory = path + 'avatars'
|
|
||||||
if not os.path.exists(directory):
|
|
||||||
os.makedirs(directory)
|
|
||||||
|
|
||||||
def open_profile(self):
|
|
||||||
with open(self._path, 'rb') as fl:
|
|
||||||
data = fl.read()
|
|
||||||
if data:
|
|
||||||
return data
|
|
||||||
else:
|
|
||||||
raise IOError('Save file has zero size!')
|
|
||||||
|
|
||||||
def get_dir(self):
|
|
||||||
return self._directory
|
|
||||||
|
|
||||||
def save_profile(self, data):
|
|
||||||
inst = LibToxEncryptSave.get_instance()
|
|
||||||
if inst.has_password():
|
|
||||||
data = inst.pass_encrypt(data)
|
|
||||||
with open(self._path, 'wb') as fl:
|
|
||||||
fl.write(data)
|
|
||||||
print 'Profile saved successfully'
|
|
||||||
|
|
||||||
def export_profile(self, new_path):
|
|
||||||
new_path += os.path.basename(self._path)
|
|
||||||
with open(self._path, 'rb') as fin:
|
|
||||||
data = fin.read()
|
|
||||||
with open(new_path, 'wb') as fout:
|
|
||||||
fout.write(data)
|
|
||||||
print 'Profile exported successfully'
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def find_profiles():
|
|
||||||
"""
|
|
||||||
Find available tox profiles
|
|
||||||
"""
|
|
||||||
path = Settings.get_default_path()
|
|
||||||
result = []
|
|
||||||
# check default path
|
|
||||||
if not os.path.exists(path):
|
|
||||||
os.makedirs(path)
|
|
||||||
for fl in os.listdir(path):
|
|
||||||
if fl.endswith('.tox'):
|
|
||||||
name = fl[:-4]
|
|
||||||
result.append((path, name))
|
|
||||||
path = curr_directory()
|
|
||||||
# check current directory
|
|
||||||
for fl in os.listdir(path):
|
|
||||||
if fl.endswith('.tox'):
|
|
||||||
name = fl[:-4]
|
|
||||||
result.append((path + '/', name))
|
|
||||||
return result
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def is_active_profile(path, name):
|
|
||||||
path = path.decode(locale.getpreferredencoding()) + name + '.tox'
|
|
||||||
settings = Settings.get_default_path() + 'toxygen.json'
|
|
||||||
if os.path.isfile(settings):
|
|
||||||
with open(settings) as fl:
|
|
||||||
data = fl.read()
|
|
||||||
data = json.loads(data)
|
|
||||||
if 'active_profile' in data:
|
|
||||||
return path in data['active_profile']
|
|
||||||
return False
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def get_path():
|
|
||||||
return ProfileHelper.get_instance().get_dir()
|
|
Before Width: | Height: | Size: 220 B |
Before Width: | Height: | Size: 172 B |
Before Width: | Height: | Size: 228 B |
Before Width: | Height: | Size: 187 B |
Before Width: | Height: | Size: 147 B |
Before Width: | Height: | Size: 160 B |
Before Width: | Height: | Size: 150 B |
Before Width: | Height: | Size: 166 B |
Before Width: | Height: | Size: 4.4 KiB |
Before Width: | Height: | Size: 4.4 KiB |
Before Width: | Height: | Size: 4.4 KiB |
Before Width: | Height: | Size: 493 B |
Before Width: | Height: | Size: 492 B |
Before Width: | Height: | Size: 514 B |
Before Width: | Height: | Size: 2.8 KiB |
Before Width: | Height: | Size: 2.8 KiB |
Before Width: | Height: | Size: 2.8 KiB |
Before Width: | Height: | Size: 598 B |
Before Width: | Height: | Size: 598 B |
Before Width: | Height: | Size: 586 B |
Before Width: | Height: | Size: 165 B |
Before Width: | Height: | Size: 166 B |
Before Width: | Height: | Size: 166 B |
Before Width: | Height: | Size: 166 B |
Before Width: | Height: | Size: 940 B |
Before Width: | Height: | Size: 972 B |
Before Width: | Height: | Size: 933 B |
Before Width: | Height: | Size: 728 B |
Before Width: | Height: | Size: 760 B |
Before Width: | Height: | Size: 724 B |
Before Width: | Height: | Size: 160 B |
Before Width: | Height: | Size: 160 B |
Before Width: | Height: | Size: 129 B |
Before Width: | Height: | Size: 224 B |
Before Width: | Height: | Size: 182 B |
Before Width: | Height: | Size: 239 B |
Before Width: | Height: | Size: 195 B |
Before Width: | Height: | Size: 578 B |
Before Width: | Height: | Size: 158 B |
Before Width: | Height: | Size: 159 B |
1510
src/tox.py
@ -1,31 +0,0 @@
|
|||||||
import json
|
|
||||||
import urllib2
|
|
||||||
from util import log
|
|
||||||
# TODO: add TOX DNS 3 support
|
|
||||||
|
|
||||||
|
|
||||||
def tox_dns(email):
|
|
||||||
"""
|
|
||||||
TOX DNS 4
|
|
||||||
:param email: data like 'groupbot@toxme.io'
|
|
||||||
:return: tox id on success else None
|
|
||||||
"""
|
|
||||||
site = email.split('@')[1]
|
|
||||||
data = {"action": 3, "name": "{}".format(email)}
|
|
||||||
for url in ('https://{}/api'.format(site), 'http://{}/api'.format(site)):
|
|
||||||
try:
|
|
||||||
return send_request(url, data)
|
|
||||||
except Exception as ex: # try http
|
|
||||||
log('TOX DNS ERROR: ' + str(ex))
|
|
||||||
return None # error
|
|
||||||
|
|
||||||
|
|
||||||
def send_request(url, data):
|
|
||||||
req = urllib2.Request(url)
|
|
||||||
req.add_header('Content-Type', 'application/json')
|
|
||||||
response = urllib2.urlopen(req, json.dumps(data))
|
|
||||||
res = json.loads(response.read())
|
|
||||||
if not res['c']:
|
|
||||||
return res['tox_id']
|
|
||||||
else:
|
|
||||||
raise LookupError()
|
|
@ -1,209 +0,0 @@
|
|||||||
TOX_USER_STATUS = {
|
|
||||||
'NONE': 0,
|
|
||||||
'AWAY': 1,
|
|
||||||
'BUSY': 2,
|
|
||||||
}
|
|
||||||
|
|
||||||
TOX_MESSAGE_TYPE = {
|
|
||||||
'NORMAL': 0,
|
|
||||||
'ACTION': 1,
|
|
||||||
}
|
|
||||||
|
|
||||||
TOX_PROXY_TYPE = {
|
|
||||||
'NONE': 0,
|
|
||||||
'HTTP': 1,
|
|
||||||
'SOCKS5': 2,
|
|
||||||
}
|
|
||||||
|
|
||||||
TOX_SAVEDATA_TYPE = {
|
|
||||||
'NONE': 0,
|
|
||||||
'TOX_SAVE': 1,
|
|
||||||
'SECRET_KEY': 2,
|
|
||||||
}
|
|
||||||
|
|
||||||
TOX_ERR_OPTIONS_NEW = {
|
|
||||||
'OK': 0,
|
|
||||||
'MALLOC': 1,
|
|
||||||
}
|
|
||||||
|
|
||||||
TOX_ERR_NEW = {
|
|
||||||
'OK': 0,
|
|
||||||
'NULL': 1,
|
|
||||||
'MALLOC': 2,
|
|
||||||
'PORT_ALLOC': 3,
|
|
||||||
'PROXY_BAD_TYPE': 4,
|
|
||||||
'PROXY_BAD_HOST': 5,
|
|
||||||
'PROXY_BAD_PORT': 6,
|
|
||||||
'PROXY_NOT_FOUND': 7,
|
|
||||||
'LOAD_ENCRYPTED': 8,
|
|
||||||
'LOAD_BAD_FORMAT': 9,
|
|
||||||
}
|
|
||||||
|
|
||||||
TOX_ERR_BOOTSTRAP = {
|
|
||||||
'OK': 0,
|
|
||||||
'NULL': 1,
|
|
||||||
'BAD_HOST': 2,
|
|
||||||
'BAD_PORT': 3,
|
|
||||||
}
|
|
||||||
|
|
||||||
TOX_CONNECTION = {
|
|
||||||
'NONE': 0,
|
|
||||||
'TCP': 1,
|
|
||||||
'UDP': 2,
|
|
||||||
}
|
|
||||||
|
|
||||||
TOX_ERR_SET_INFO = {
|
|
||||||
'OK': 0,
|
|
||||||
'NULL': 1,
|
|
||||||
'TOO_LONG': 2,
|
|
||||||
}
|
|
||||||
|
|
||||||
TOX_ERR_FRIEND_ADD = {
|
|
||||||
'OK': 0,
|
|
||||||
'NULL': 1,
|
|
||||||
'TOO_LONG': 2,
|
|
||||||
'NO_MESSAGE': 3,
|
|
||||||
'OWN_KEY': 4,
|
|
||||||
'ALREADY_SENT': 5,
|
|
||||||
'BAD_CHECKSUM': 6,
|
|
||||||
'SET_NEW_NOSPAM': 7,
|
|
||||||
'MALLOC': 8,
|
|
||||||
}
|
|
||||||
|
|
||||||
TOX_ERR_FRIEND_DELETE = {
|
|
||||||
'OK': 0,
|
|
||||||
'FRIEND_NOT_FOUND': 1,
|
|
||||||
}
|
|
||||||
|
|
||||||
TOX_ERR_FRIEND_BY_PUBLIC_KEY = {
|
|
||||||
'OK': 0,
|
|
||||||
'NULL': 1,
|
|
||||||
'NOT_FOUND': 2,
|
|
||||||
}
|
|
||||||
|
|
||||||
TOX_ERR_FRIEND_GET_PUBLIC_KEY = {
|
|
||||||
'OK': 0,
|
|
||||||
'FRIEND_NOT_FOUND': 1,
|
|
||||||
}
|
|
||||||
|
|
||||||
TOX_ERR_FRIEND_GET_LAST_ONLINE = {
|
|
||||||
'OK': 0,
|
|
||||||
'FRIEND_NOT_FOUND': 1,
|
|
||||||
}
|
|
||||||
|
|
||||||
TOX_ERR_FRIEND_QUERY = {
|
|
||||||
'OK': 0,
|
|
||||||
'NULL': 1,
|
|
||||||
'FRIEND_NOT_FOUND': 2,
|
|
||||||
}
|
|
||||||
|
|
||||||
TOX_ERR_SET_TYPING = {
|
|
||||||
'OK': 0,
|
|
||||||
'FRIEND_NOT_FOUND': 1,
|
|
||||||
}
|
|
||||||
|
|
||||||
TOX_ERR_FRIEND_SEND_MESSAGE = {
|
|
||||||
'OK': 0,
|
|
||||||
'NULL': 1,
|
|
||||||
'FRIEND_NOT_FOUND': 2,
|
|
||||||
'FRIEND_NOT_CONNECTED': 3,
|
|
||||||
'SENDQ': 4,
|
|
||||||
'TOO_LONG': 5,
|
|
||||||
'EMPTY': 6,
|
|
||||||
}
|
|
||||||
|
|
||||||
TOX_FILE_KIND = {
|
|
||||||
'DATA': 0,
|
|
||||||
'AVATAR': 1,
|
|
||||||
}
|
|
||||||
|
|
||||||
TOX_FILE_CONTROL = {
|
|
||||||
'RESUME': 0,
|
|
||||||
'PAUSE': 1,
|
|
||||||
'CANCEL': 2,
|
|
||||||
}
|
|
||||||
|
|
||||||
TOX_ERR_FILE_CONTROL = {
|
|
||||||
'OK': 0,
|
|
||||||
'FRIEND_NOT_FOUND': 1,
|
|
||||||
'FRIEND_NOT_CONNECTED': 2,
|
|
||||||
'NOT_FOUND': 3,
|
|
||||||
'NOT_PAUSED': 4,
|
|
||||||
'DENIED': 5,
|
|
||||||
'ALREADY_PAUSED': 6,
|
|
||||||
'SENDQ': 7,
|
|
||||||
}
|
|
||||||
|
|
||||||
TOX_ERR_FILE_SEEK = {
|
|
||||||
'OK': 0,
|
|
||||||
'FRIEND_NOT_FOUND': 1,
|
|
||||||
'FRIEND_NOT_CONNECTED': 2,
|
|
||||||
'NOT_FOUND': 3,
|
|
||||||
'DENIED': 4,
|
|
||||||
'INVALID_POSITION': 5,
|
|
||||||
'SENDQ': 6,
|
|
||||||
}
|
|
||||||
|
|
||||||
TOX_ERR_FILE_GET = {
|
|
||||||
'OK': 0,
|
|
||||||
'NULL': 1,
|
|
||||||
'FRIEND_NOT_FOUND': 2,
|
|
||||||
'NOT_FOUND': 3,
|
|
||||||
}
|
|
||||||
|
|
||||||
TOX_ERR_FILE_SEND = {
|
|
||||||
'OK': 0,
|
|
||||||
'NULL': 1,
|
|
||||||
'FRIEND_NOT_FOUND': 2,
|
|
||||||
'FRIEND_NOT_CONNECTED': 3,
|
|
||||||
'NAME_TOO_LONG': 4,
|
|
||||||
'TOO_MANY': 5,
|
|
||||||
}
|
|
||||||
|
|
||||||
TOX_ERR_FILE_SEND_CHUNK = {
|
|
||||||
'OK': 0,
|
|
||||||
'NULL': 1,
|
|
||||||
'FRIEND_NOT_FOUND': 2,
|
|
||||||
'FRIEND_NOT_CONNECTED': 3,
|
|
||||||
'NOT_FOUND': 4,
|
|
||||||
'NOT_TRANSFERRING': 5,
|
|
||||||
'INVALID_LENGTH': 6,
|
|
||||||
'SENDQ': 7,
|
|
||||||
'WRONG_POSITION': 8,
|
|
||||||
}
|
|
||||||
|
|
||||||
TOX_ERR_FRIEND_CUSTOM_PACKET = {
|
|
||||||
'OK': 0,
|
|
||||||
'NULL': 1,
|
|
||||||
'FRIEND_NOT_FOUND': 2,
|
|
||||||
'FRIEND_NOT_CONNECTED': 3,
|
|
||||||
'INVALID': 4,
|
|
||||||
'EMPTY': 5,
|
|
||||||
'TOO_LONG': 6,
|
|
||||||
'SENDQ': 7,
|
|
||||||
}
|
|
||||||
|
|
||||||
TOX_ERR_GET_PORT = {
|
|
||||||
'OK': 0,
|
|
||||||
'NOT_BOUND': 1,
|
|
||||||
}
|
|
||||||
|
|
||||||
TOX_PUBLIC_KEY_SIZE = 32
|
|
||||||
|
|
||||||
TOX_ADDRESS_SIZE = TOX_PUBLIC_KEY_SIZE + 6
|
|
||||||
|
|
||||||
TOX_MAX_FRIEND_REQUEST_LENGTH = 1016
|
|
||||||
|
|
||||||
TOX_MAX_MESSAGE_LENGTH = 1372
|
|
||||||
|
|
||||||
TOX_MAX_NAME_LENGTH = 128
|
|
||||||
|
|
||||||
TOX_MAX_STATUS_MESSAGE_LENGTH = 1007
|
|
||||||
|
|
||||||
TOX_SECRET_KEY_SIZE = 32
|
|
||||||
|
|
||||||
TOX_FILE_ID_LENGTH = 32
|
|
||||||
|
|
||||||
TOX_HASH_LENGTH = 32
|
|
||||||
|
|
||||||
TOX_MAX_CUSTOM_PACKET_SIZE = 1373
|
|
@ -1,2 +0,0 @@
|
|||||||
SOURCES = main.py profile.py menu.py list_items.py loginscreen.py mainscreen.py plugins/plugin_super_class.py
|
|
||||||
TRANSLATIONS = translations/en_GB.ts translations/ru_RU.ts translations/fr_FR.ts
|
|