Compare commits
781 commits
v1.1.3-plu
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
519d86444a | ||
|
|
2485227701 | ||
|
|
275e719da3 | ||
|
|
1675161095 | ||
|
|
4394bb1174 | ||
|
|
fdfc9c90a1 | ||
|
|
7ec93f89a2 | ||
|
|
e1bcbd2f1f | ||
|
|
02915163dc | ||
|
|
2e50ac5205 | ||
|
|
22b3388554 | ||
|
|
f601ca0d7b | ||
|
|
9dbac0f098 | ||
|
|
9f8492ae69 | ||
|
|
c823ef6ceb | ||
|
|
ac4bde0447 | ||
|
|
03917eda66 | ||
|
|
bc5e754172 | ||
|
|
b59d5434f2 | ||
|
|
9e25930106 | ||
|
|
7ed770d7c8 | ||
|
|
606b5e8a2b | ||
|
|
5fb231876d | ||
|
|
ba1f88a006 | ||
|
|
a50cedaa6b | ||
|
|
2eb2039a3f | ||
|
|
b9b5690bc4 | ||
|
|
423cfb9fdc | ||
|
|
dd7496d7a7 | ||
|
|
6c6c4bdcfe | ||
|
|
2f5f32257d | ||
|
|
a8bd8abe11 | ||
|
|
6e9b605c00 | ||
|
|
5d795b4c1e | ||
|
|
f6c3d03261 | ||
|
|
87bbf4738a | ||
|
|
b262d5add9 | ||
|
|
eb6762a461 | ||
|
|
04717274b3 | ||
|
|
245cecf1a9 | ||
|
|
a9499386be | ||
|
|
185d249e0f | ||
|
|
30997fa1e4 | ||
|
|
b306a7e05f | ||
|
|
8c03d37217 | ||
|
|
657050ad52 | ||
|
|
5f84dffca6 | ||
|
|
df13aa4b22 | ||
|
|
b134fccf94 | ||
|
|
e34886ae47 | ||
|
|
e495617d3d | ||
|
|
c343746430 | ||
|
|
cc3750711d | ||
|
|
6e7f53df48 | ||
|
|
cbb1918bce | ||
|
|
eb6d324ec5 | ||
|
|
a27599c63b | ||
|
|
8bfe90c5fe | ||
|
|
9f4adbdd1c | ||
|
|
bd75c78b82 | ||
|
|
24352b57c7 | ||
|
|
b2b249c60b | ||
|
|
731aaab633 | ||
|
|
e999b74a5b | ||
|
|
8f3604016f | ||
|
|
df71c623e0 | ||
|
|
9d75c77982 | ||
|
|
64bac1d910 | ||
|
|
2f9b716ab0 | ||
|
|
ef1d7771fb | ||
|
|
53880edd96 | ||
|
|
b32b0f4411 | ||
|
|
3312ca6c29 | ||
|
|
ecdf1526fb | ||
|
|
d8beb0183c | ||
|
|
d83f725578 | ||
|
|
a717ec931e | ||
|
|
1e47cfaa57 | ||
|
|
7b329e9657 | ||
|
|
0dbcac4efe | ||
|
|
de790f5e4c | ||
|
|
64d1401307 | ||
|
|
4ac532c3fa | ||
|
|
24d939ce41 | ||
|
|
3808c85cbd | ||
|
|
b54aba4d3e | ||
|
|
b91b64997e | ||
|
|
33f62cecf9 | ||
|
|
b8d70abdb0 | ||
|
|
7ef066b181 | ||
|
|
cf40a2a8ef | ||
|
|
3b3c2af97b | ||
|
|
d55cb97dd2 | ||
|
|
f62f922733 | ||
|
|
1c39d66d21 | ||
|
|
ee38f76004 | ||
|
|
20d3e981b9 | ||
|
|
dc3910ce4c | ||
|
|
8ca9c1d6e3 | ||
|
|
5471980a8c | ||
|
|
0dbb3eb1d5 | ||
|
|
c8815a9230 | ||
|
|
30f850b567 | ||
|
|
b09f94dabd | ||
|
|
47dae61e21 | ||
|
|
3b2211776c | ||
|
|
45ac229fb1 | ||
|
|
32168c60f5 | ||
|
|
797a9518ea | ||
|
|
ed011da59e | ||
|
|
06848eaeaf | ||
|
|
ac6eae8965 | ||
|
|
f1642e5327 | ||
|
|
0b9aeb0100 | ||
|
|
03cc3379d5 | ||
|
|
318ae4e9d4 | ||
|
|
239ab1455b | ||
|
|
471d9ca043 | ||
|
|
0678272774 | ||
|
|
4d43eacfd0 | ||
|
|
a48610632e | ||
|
|
e83a246703 | ||
|
|
139156ba5e | ||
|
|
16bbe31df6 | ||
|
|
a7bc6dc530 | ||
|
|
ae29c937ba | ||
|
|
f838c2faa2 | ||
|
|
2080495efc | ||
|
|
74ed71f57e | ||
|
|
760b94f27f | ||
|
|
4c062bf93d | ||
|
|
bbada2ecd4 | ||
|
|
ca037665ca | ||
|
|
acd9f67d92 | ||
|
|
21aa352a34 | ||
|
|
1551ffa0b4 | ||
|
|
8201688684 | ||
|
|
7a5d791beb | ||
|
|
a9efde136b | ||
|
|
b6e7f5c6d2 | ||
|
|
4acf36c97b | ||
|
|
f6e113c0b6 | ||
|
|
52111cd1b3 | ||
|
|
a988d2e250 | ||
|
|
0de04b1ae9 | ||
|
|
fd46fcab64 | ||
|
|
c2be5372bd | ||
|
|
ae4c98cbb2 | ||
|
|
5af7fbd428 | ||
|
|
ab6623d7bd | ||
|
|
6ef3b8e01b | ||
|
|
05dc004448 | ||
|
|
c17b744465 | ||
|
|
196d856a54 | ||
|
|
be26f50120 | ||
|
|
fa464d4333 | ||
|
|
979c7d2553 | ||
|
|
11ae4fac6a | ||
|
|
62d12f4355 | ||
|
|
4b3a5b7704 | ||
|
|
348a8551fe | ||
|
|
da46bdda1f | ||
|
|
f5fe4fd8de | ||
|
|
851a7ee929 | ||
|
|
b80bbaf2b3 | ||
|
|
0eb75eb73d | ||
|
|
6e672c9911 | ||
|
|
b704e27abc | ||
|
|
5e97d805d9 | ||
|
|
872ee3d675 | ||
|
|
1827249653 | ||
|
|
74c5f3d0e9 | ||
|
|
ee6f65f404 | ||
|
|
94eef409db | ||
|
|
c58c7cc4a0 | ||
|
|
1f479e5ba4 | ||
|
|
d61ca3ae6d | ||
|
|
e023c33268 | ||
|
|
553436332b | ||
|
|
6c06d2c497 | ||
|
|
3bf755943f | ||
|
|
e3f8f050c0 | ||
|
|
0963f44443 | ||
|
|
12d909d636 | ||
|
|
12389f4097 | ||
|
|
72fc96ae63 | ||
|
|
11c5fd29e5 | ||
|
|
54a9992696 | ||
|
|
67ddbf73cd | ||
|
|
7530b231b1 | ||
|
|
0573d42d29 | ||
|
|
7cd781e921 | ||
|
|
4c3110c27d | ||
|
|
a944e7e6e1 | ||
|
|
8455a0fa40 | ||
|
|
53d6b1dc32 | ||
|
|
7fe03dcb45 | ||
|
|
bc7e0e1ee4 | ||
|
|
1f5be6cb0f | ||
|
|
c2038b0ffe | ||
|
|
dede4ba4f2 | ||
|
|
94160d830f | ||
|
|
f8373012a2 | ||
|
|
4a2c23b893 | ||
|
|
6f61f05377 | ||
|
|
1256372d36 | ||
|
|
5059f72373 | ||
|
|
4442db5461 | ||
|
|
fc8c056eff | ||
|
|
fa71052464 | ||
|
|
959be5b8ee | ||
|
|
36a0083942 | ||
|
|
a3c316abaa | ||
|
|
a89bec0b9d | ||
|
|
fa7f6f2b8c | ||
|
|
cef5b04d82 | ||
|
|
15dc059bf3 | ||
|
|
dfc3749a4f | ||
|
|
3f03e6e0a4 | ||
|
|
59b39b049a | ||
|
|
d37b95e45e | ||
|
|
18c422d843 | ||
|
|
0b6fac44dc | ||
|
|
b9b9dae6a0 | ||
|
|
be95b04007 | ||
|
|
8e336311cc | ||
|
|
d6b7916f2b | ||
|
|
e994b8f402 | ||
|
|
80e5934bb5 | ||
|
|
ce0abb991c | ||
|
|
dad99f85fc | ||
|
|
1a7d930fc4 | ||
|
|
b0742377f0 | ||
|
|
4670760668 | ||
|
|
31d9b39744 | ||
|
|
3e362874be | ||
|
|
7c7df9d4a4 | ||
|
|
cf8eafffb2 | ||
|
|
a9994b791b | ||
|
|
72be14a04d | ||
|
|
b22f1baca5 | ||
|
|
4f2f2195aa | ||
|
|
88439091b0 | ||
|
|
911674d580 | ||
|
|
5b1f8bcc48 | ||
|
|
418459b634 | ||
|
|
247ee81334 | ||
|
|
b84054b58c | ||
|
|
de25b57ad8 | ||
|
|
dad671c1c7 | ||
|
|
fc64bee08c | ||
|
|
4f3e4d2242 | ||
|
|
84b2392fcc | ||
|
|
9879722d42 | ||
|
|
a917ea18e2 | ||
|
|
ab1a64b352 | ||
|
|
de8b5cb7a6 | ||
|
|
5776d3de48 | ||
|
|
6a958c5725 | ||
|
|
a64d986c7f | ||
|
|
eb99291d44 | ||
|
|
777b8d72e4 | ||
|
|
e308d7a210 | ||
|
|
eebccaf07d | ||
|
|
55694b60bf | ||
|
|
4c17b55a91 | ||
|
|
4aa265451d | ||
|
|
7b558d692c | ||
|
|
19c45510a0 | ||
|
|
633795b7f4 | ||
|
|
a91f5f2c49 | ||
|
|
468d8bd472 | ||
|
|
58d7096aca | ||
|
|
d200e94fba | ||
|
|
4aca2eeae2 | ||
|
|
4bd14bc51d | ||
|
|
67318a49e5 | ||
|
|
5fd1e7b4bf | ||
|
|
b600328639 | ||
|
|
cdb37828e4 | ||
|
|
c0fb645309 | ||
|
|
4631f50372 | ||
|
|
eb9f340a03 | ||
|
|
b05f0e1458 | ||
|
|
0441ccd4e9 | ||
|
|
f3d5babc65 | ||
|
|
1399c41d59 | ||
|
|
371756c2a5 | ||
|
|
fcb33a10e7 | ||
|
|
d1ccfd515a | ||
|
|
d622439a44 | ||
|
|
9ae6654dd3 | ||
|
|
d93fe8b1ea | ||
|
|
d121bbf4ab | ||
|
|
b37c578504 | ||
|
|
0fcf90f8d5 | ||
|
|
f2beea0df3 | ||
|
|
a289ad5f60 | ||
|
|
608607b721 | ||
|
|
a7aea4cac1 | ||
|
|
735e72c965 | ||
|
|
73f3e01bdf | ||
|
|
d06b73d7c6 | ||
|
|
e1c17d752e | ||
|
|
d203d36f07 | ||
|
|
0ad369dc97 | ||
|
|
ed4a81fead | ||
|
|
5a148f1657 | ||
|
|
79684d90d5 | ||
|
|
34306921ed | ||
|
|
8094cadbea | ||
|
|
c63e32634a | ||
|
|
b548b90a6c | ||
|
|
94479dd7f6 | ||
|
|
43f210bbaa | ||
|
|
0e6011c088 | ||
|
|
4503f466d6 | ||
|
|
40fc727156 | ||
|
|
85e79e7b27 | ||
|
|
42c1909d3b | ||
|
|
1f975df8a4 | ||
|
|
c614ac69df | ||
|
|
8b5adb06da | ||
|
|
56d2c6bc7e | ||
|
|
ce6839957d | ||
|
|
5f555193c7 | ||
|
|
e9f4c5050e | ||
|
|
1fec6681cd | ||
|
|
b935ce7096 | ||
|
|
99cfb8f629 | ||
|
|
de4cd80b83 | ||
|
|
ced387e719 | ||
|
|
7286f4286f | ||
|
|
89d9940933 | ||
|
|
0ac52a390e | ||
|
|
a6b8ecf6f5 | ||
|
|
b449567600 | ||
|
|
841f12f75b | ||
|
|
96edd79963 | ||
|
|
19b0e73f44 | ||
|
|
674ec48fa2 | ||
|
|
760645f5c3 | ||
|
|
dac5370780 | ||
|
|
5843827abc | ||
|
|
be420d44fc | ||
|
|
4e814dabcf | ||
|
|
6e30d3af64 | ||
|
|
b8863e82be | ||
|
|
293156f670 | ||
|
|
d129f5559a | ||
|
|
31417ffbc7 | ||
|
|
ace1113d02 | ||
|
|
a979874fa1 | ||
|
|
e419f75217 | ||
|
|
4968fe39e2 | ||
|
|
62253c804a | ||
|
|
c2e2dd9a87 | ||
|
|
86bbdd21fd | ||
|
|
932377f533 | ||
|
|
700666b820 | ||
|
|
78f439c611 | ||
|
|
59855a6c8d | ||
|
|
fd6dec1208 | ||
|
|
1d11e06647 | ||
|
|
3f2e3c6bb2 | ||
|
|
432a4edbc1 | ||
|
|
91ebe37359 | ||
|
|
f1ab9fb683 | ||
|
|
f942e2c70f | ||
|
|
e70ea74f0d | ||
|
|
5fa56684c7 | ||
|
|
cb05671254 | ||
|
|
1196e1df0d | ||
|
|
35b5a4d7a9 | ||
|
|
6327679461 | ||
|
|
c97fddf0f8 | ||
|
|
fa0f35da01 | ||
|
|
c8b5eb94a9 | ||
|
|
8f9c596a0c | ||
|
|
15c3de2b4d | ||
|
|
b74468b9c3 | ||
|
|
c61bd65607 | ||
|
|
9d8b56bc0f | ||
|
|
a00e8e3abb | ||
|
|
e305069798 | ||
|
|
b5bd5f15f0 | ||
|
|
1504805575 | ||
|
|
596f268f14 | ||
|
|
b98f0a3f76 | ||
|
|
3c32d51174 | ||
|
|
dffa54f7d3 | ||
|
|
fedc00eb83 | ||
|
|
2ece342de2 | ||
|
|
1179333fa0 | ||
|
|
aca34d5c35 | ||
|
|
3ba34fa396 | ||
|
|
619793a085 | ||
|
|
acc89ad622 | ||
|
|
8be5562b92 | ||
|
|
d13baed80f | ||
|
|
f40f171ecd | ||
|
|
b5b3820328 | ||
|
|
431d8075a6 | ||
|
|
cee63aa0d8 | ||
|
|
a1b798b8fc | ||
|
|
dd0b7260b4 | ||
|
|
5c0ff1de9f | ||
|
|
fb5cee69cb | ||
|
|
80954e9534 | ||
|
|
eedd0c2a72 | ||
|
|
97310a059a | ||
|
|
63ff9478bd | ||
|
|
77d64267c7 | ||
|
|
7f1dfe5a8c | ||
|
|
4be4998704 | ||
|
|
9e24c72ba5 | ||
|
|
8f6dfad44e | ||
|
|
9c5de3bf25 | ||
|
|
f26db88424 | ||
|
|
e281fb6839 | ||
|
|
989926a680 | ||
|
|
522eec007d | ||
|
|
49758ab30e | ||
|
|
5ecdfbee5c | ||
|
|
ac5bfe2d7c | ||
|
|
e509a68139 | ||
|
|
62b210a9e5 | ||
|
|
30a20e16d2 | ||
|
|
e4a9ffe1ca | ||
|
|
02a9615b94 | ||
|
|
a7cc815ca4 | ||
|
|
5152611ad7 | ||
|
|
0d70352460 | ||
|
|
1f31ab04f2 | ||
|
|
8d95eb9b99 | ||
|
|
ac2a75db48 | ||
|
|
09cded2ff3 | ||
|
|
62cfd7293a | ||
|
|
4534f3dbac | ||
|
|
13c82ae777 | ||
|
|
bd7e32187d | ||
|
|
fd97d64205 | ||
|
|
02f16d53f4 | ||
|
|
56f948f56b | ||
|
|
43f07c19e5 | ||
|
|
d106c0d316 | ||
|
|
ea2f76f24e | ||
|
|
f5a19c24a9 | ||
|
|
b4d34bf7f6 | ||
|
|
2ec1baa7bd | ||
|
|
b6c4fcfb54 | ||
|
|
3e387f5215 | ||
|
|
4753e6015e | ||
|
|
6b65a23371 | ||
|
|
44d56ac2fa | ||
|
|
09f0835fbd | ||
|
|
4e0295fcd9 | ||
|
|
6e41699297 | ||
|
|
5d5d686408 | ||
|
|
21656ebfe8 | ||
|
|
bf9e01ee31 | ||
|
|
61a791d67c | ||
|
|
e615a8e2e3 | ||
|
|
0662374c14 | ||
|
|
32e736aa32 | ||
|
|
7cae880920 | ||
|
|
55eef51887 | ||
|
|
110da0172f | ||
|
|
7d5f1fba50 | ||
|
|
1aba2b2d76 | ||
|
|
c2521a41d6 | ||
|
|
f46fdee464 | ||
|
|
4736f518d6 | ||
|
|
3eeed7d2b4 | ||
|
|
985fced940 | ||
|
|
7a070a583e | ||
|
|
3ae03a24b1 | ||
|
|
a6d9b2c193 | ||
|
|
e958755a24 | ||
|
|
0e3b74da16 | ||
|
|
ac3bc74952 | ||
|
|
5769ca1e29 | ||
|
|
2a0d6dc1ad | ||
|
|
69309d283d | ||
|
|
95e67ab7c1 | ||
|
|
cdeb902fda | ||
|
|
9ae63b6f70 | ||
|
|
dcfaba09b9 | ||
|
|
f4a7e2a353 | ||
|
|
59ccac4831 | ||
|
|
cb58fc86f0 | ||
|
|
efbd22723c | ||
|
|
88f30f92f7 | ||
|
|
39dfe41af5 | ||
|
|
b5441b6169 | ||
|
|
9af4b4f016 | ||
|
|
4e2f00e569 | ||
|
|
c109db6eeb | ||
|
|
6b5d559d7a | ||
|
|
960008a69a | ||
|
|
9734e7bdea | ||
|
|
e86c619379 | ||
|
|
a4077502d5 | ||
|
|
7faebf2be5 | ||
|
|
5c97f0a91c | ||
|
|
a1701301f7 | ||
|
|
a75c0cbd48 | ||
|
|
bed18c9067 | ||
|
|
5fb6032420 | ||
|
|
2a62209e1e | ||
|
|
54fe4dacf2 | ||
|
|
e5beff266a | ||
|
|
ef167349f8 | ||
|
|
df7f0324a3 | ||
|
|
d98ba45f97 | ||
|
|
5d2e83394b | ||
|
|
b5416f0e97 | ||
|
|
08fe232695 | ||
|
|
3dcd6323d2 | ||
|
|
d7ea69f36c | ||
|
|
c1cc8c5714 | ||
|
|
c08f752d89 | ||
|
|
2c6b2e0bb4 | ||
|
|
974b351338 | ||
|
|
0e2a9c724f | ||
|
|
79a37f6992 | ||
|
|
9184c66eae | ||
|
|
fe21ae6086 | ||
|
|
9665376247 | ||
|
|
dc59801d93 | ||
|
|
286f820f77 | ||
|
|
4f28720db0 | ||
|
|
7397cc8af6 | ||
|
|
af88c4cbc6 | ||
|
|
3a97d44e3e | ||
|
|
c2d03dbf24 | ||
|
|
0d4ba65434 | ||
|
|
2c01883fc0 | ||
|
|
e44cedf094 | ||
|
|
abf1909360 | ||
|
|
53ec34a955 | ||
|
|
dbd3165c00 | ||
|
|
8487bdb83e | ||
|
|
ba2618d035 | ||
|
|
c34ce28f56 | ||
|
|
c58ea13379 | ||
|
|
7113760b03 | ||
|
|
2b7006e883 | ||
|
|
683e7aa4c7 | ||
|
|
6c43c0f408 | ||
|
|
c43714e4c2 | ||
|
|
e5fe30cb5b | ||
|
|
00c8e5c01d | ||
|
|
ff0e02685a | ||
|
|
528f758287 | ||
|
|
8a0ed4af5b | ||
|
|
f5337c6890 | ||
|
|
c81711fdf0 | ||
|
|
bae95858aa | ||
|
|
491237b41d | ||
|
|
f565928890 | ||
|
|
7e007a4210 | ||
|
|
84276aed43 | ||
|
|
a60b54e46e | ||
|
|
0c0d852afe | ||
|
|
084e5d6ca7 | ||
|
|
99972dacf7 | ||
|
|
aa50da05d3 | ||
|
|
f10f7d27ad | ||
|
|
cf7ba8d790 | ||
|
|
cea5231c79 | ||
|
|
a26c4f164d | ||
|
|
74bb8cec3f | ||
|
|
ca46742117 | ||
|
|
ad4e1258d3 | ||
|
|
bf87ad5ac8 | ||
|
|
990a35f9d8 | ||
|
|
fd232ce616 | ||
|
|
259f5e3674 | ||
|
|
dbd25c590d | ||
|
|
1e933c2681 | ||
|
|
e8c5ad64a7 | ||
|
|
b049746418 | ||
|
|
4f1a3a32c5 | ||
|
|
84ba852a97 | ||
|
|
34553b6ca7 | ||
|
|
5173ca415a | ||
|
|
e664185845 | ||
|
|
8b7aae564b | ||
|
|
b6c43c9066 | ||
|
|
237abf742b | ||
|
|
ef3cc504d2 | ||
|
|
ff424b5084 | ||
|
|
4858bb46f2 | ||
|
|
460d390795 | ||
|
|
07b175d541 | ||
|
|
5be73fa824 | ||
|
|
1f3bb5667d | ||
|
|
ef4c63ba3e | ||
|
|
6975ffdf94 | ||
|
|
acf79ffe8a | ||
|
|
bdd243d045 | ||
|
|
a8c0567bd0 | ||
|
|
24e771266b | ||
|
|
8ff3e55f33 | ||
|
|
cc891be187 | ||
|
|
58796d223e | ||
|
|
34e93bba17 | ||
|
|
e9e0b7fbb9 | ||
|
|
6bf94f15d7 | ||
|
|
075069d37a | ||
|
|
f89347516f | ||
|
|
b2a7c9495d | ||
|
|
429a176712 | ||
|
|
8e0a60911a | ||
|
|
5aa41c7f63 | ||
|
|
7cacb4c09e | ||
|
|
0e0bb9e190 | ||
|
|
26b8125446 | ||
|
|
39f5803651 | ||
|
|
d29f6b2ca9 | ||
|
|
06302cdc4d | ||
|
|
4077e0d6d6 | ||
|
|
1483020cf4 | ||
|
|
011d14e44b | ||
|
|
0f730d1958 | ||
|
|
6b2aa24d6e | ||
|
|
d722a8558b | ||
|
|
b70549dd59 | ||
|
|
74b66837fa | ||
|
|
f7a2ac9d90 | ||
|
|
4d4d002342 | ||
|
|
3f2f51c169 | ||
|
|
5a906c1454 | ||
|
|
efca9cd9c7 | ||
|
|
0a66b6d9ad | ||
|
|
94b17b1fbc | ||
|
|
fa65653adc | ||
|
|
cb692ae4eb | ||
|
|
c7c74998f2 | ||
|
|
46ed4757fd | ||
|
|
5b23d4879a | ||
|
|
7fcd85f026 | ||
|
|
422cd12991 | ||
|
|
6c80473ebe | ||
|
|
803ff3de45 | ||
|
|
385ef2e456 | ||
|
|
457faf3806 | ||
|
|
81387ada51 | ||
|
|
c2828231bd | ||
|
|
c3e897bdb4 | ||
|
|
6b54863157 | ||
|
|
31a500245f | ||
|
|
8fad65ccbf | ||
|
|
13a88533da | ||
|
|
1680c3bb38 | ||
|
|
251dbae5d6 | ||
|
|
2512c2b484 | ||
|
|
55aab55b9b | ||
|
|
a77804f475 | ||
|
|
8483ba503f | ||
|
|
376fff7871 | ||
|
|
df88cc5e15 | ||
|
|
29647ea693 | ||
|
|
ccc06a04ab | ||
|
|
2acfcfd9f9 | ||
|
|
dea1464832 | ||
|
|
6237bfae41 | ||
|
|
66c8c26bbc | ||
|
|
ff6ca57033 | ||
|
|
d6f0242655 | ||
|
|
cbe335b291 | ||
|
|
206b87d7b7 | ||
|
|
04b70bf220 | ||
|
|
67bb5a57c3 | ||
|
|
01694c6a06 | ||
|
|
158d013a86 | ||
|
|
76333ec73a | ||
|
|
55febc4963 | ||
|
|
793184e7be | ||
|
|
0b8b4f75c3 | ||
|
|
71feb0b2d2 | ||
|
|
e8ac644440 | ||
|
|
c6e2335bf0 | ||
|
|
7bdad5f4e2 | ||
|
|
d638087c26 | ||
|
|
9bab139bb5 | ||
|
|
7bb6f8b22e | ||
|
|
b8bbcf2be0 | ||
|
|
aaaa3bc9bd | ||
|
|
f6144bff30 | ||
|
|
d53b95218a | ||
|
|
adc94bdcc6 | ||
|
|
c6547b6d30 | ||
|
|
328d250f4c | ||
|
|
b4ac6db9b9 | ||
|
|
d19ca8113a | ||
|
|
7018d5be0e | ||
|
|
e85bc52c8b | ||
|
|
1462f89229 | ||
|
|
bb545d546a | ||
|
|
9e51ed8c7f | ||
|
|
de0c0a9c08 | ||
|
|
50e44543f3 | ||
|
|
8d9fe9e2e1 | ||
|
|
49ca238252 | ||
|
|
f7a139eac2 | ||
|
|
97ee8082fe | ||
|
|
eca69e399b | ||
|
|
3b5b4574ba | ||
|
|
64840cd0b0 | ||
|
|
d54d4bc232 | ||
|
|
a62f348a21 | ||
|
|
58a3b83083 | ||
|
|
62c54525d8 | ||
|
|
64aec8adce | ||
|
|
ee254d25d2 | ||
|
|
6b221e7059 | ||
|
|
b99632ab03 | ||
|
|
691678b06a | ||
|
|
c9455de4bd | ||
|
|
09c5183575 | ||
|
|
3080946ea1 | ||
|
|
4ad38d5398 | ||
|
|
dad7def794 | ||
|
|
a34bf54c46 | ||
|
|
21672fadee | ||
|
|
2649b3d01e | ||
|
|
ca9b4cb596 | ||
|
|
bcf2a3770d | ||
|
|
7140c2d2a1 | ||
|
|
6ac31ba5b6 | ||
|
|
0fba2b8a58 | ||
|
|
f82cd95f2c | ||
|
|
432704005d | ||
|
|
a497051965 | ||
|
|
0a20cb5997 | ||
|
|
27dd36cca3 | ||
|
|
52e09cf526 | ||
|
|
c8b31dffbb | ||
|
|
bba88cf609 | ||
|
|
3a16ccb4d4 | ||
|
|
05307bd609 | ||
|
|
a4a136566f | ||
|
|
856a874dd5 | ||
|
|
f91408fbaf | ||
|
|
9dd747dfe5 | ||
|
|
a6631398f7 | ||
|
|
27f7580688 | ||
|
|
b62335eaf5 | ||
|
|
a9a115d359 | ||
|
|
b8444fec64 | ||
|
|
b56f9c6fac | ||
|
|
be6c2d4f7a | ||
|
|
c68f4f1041 | ||
|
|
7d723d7b98 | ||
|
|
be96422a14 | ||
|
|
527762f726 | ||
|
|
7743c11a50 | ||
|
|
dd8dda9b32 | ||
|
|
8a34261137 | ||
|
|
08d0cde95d | ||
|
|
7d4a9b91c8 | ||
|
|
931e3f60ba | ||
|
|
3c492e92da | ||
|
|
d57bfcbcad | ||
|
|
e665ee1fbe | ||
|
|
bf64d04046 | ||
|
|
a087486e45 | ||
|
|
fb4c5a2351 | ||
|
|
fef8cff254 | ||
|
|
2a4e59654f | ||
|
|
5d853e902a | ||
|
|
e128a3f676 | ||
|
|
8ab607629b | ||
|
|
1c69b92d85 | ||
|
|
f3dd55de3e | ||
|
|
9e3e6f80bb | ||
|
|
854f8512a4 | ||
|
|
a592fe5b6a | ||
|
|
732ed0a162 |
817 changed files with 29412 additions and 29540 deletions
5
.flake8
5
.flake8
|
|
@ -1,5 +0,0 @@
|
|||
[flake8]
|
||||
ignore =
|
||||
exclude = .git,__pycache__,.gitlab
|
||||
max-complexity = 15
|
||||
builtins=_
|
||||
3
.gitignore
vendored
3
.gitignore
vendored
|
|
@ -7,6 +7,7 @@ syntax: glob
|
|||
*.pyc
|
||||
*.pyo
|
||||
*~
|
||||
*#
|
||||
autom4te.cache
|
||||
data/org.gajim.Gajim.appdata.xml
|
||||
data/org.gajim.Gajim.desktop
|
||||
|
|
@ -19,11 +20,13 @@ po/stamp-it
|
|||
stamp-h1
|
||||
Makefile
|
||||
__pycache__/
|
||||
.mypy_cache/
|
||||
build/
|
||||
dist/
|
||||
gajim.egg-info/
|
||||
src/gtk/
|
||||
win/_build_root
|
||||
.vscode
|
||||
|
||||
syntax: regexp
|
||||
^config\.*
|
||||
|
|
|
|||
|
|
@ -1,20 +1,30 @@
|
|||
before_script:
|
||||
- sudo apt-get update -qq && sudo apt-get install -y -qq libtool pkg-config python3-openssl gir1.2-gtk-3.0 python3-gi python3-nbxmpp-nightly pylint3
|
||||
- sudo apt-get update -qq && sudo apt-get install -y -qq libtool pkg-config gir1.2-gtk-3.0 python3-gi python3-cairo
|
||||
- sudo apt-get build-dep -y -qq gajim-default-nightly
|
||||
|
||||
stages:
|
||||
- test
|
||||
- build
|
||||
|
||||
run-test:
|
||||
run-tests:
|
||||
stage: test
|
||||
script:
|
||||
- python3 setup.py test_nogui
|
||||
- rm -rf civenv-master
|
||||
- virtualenv --system-site-packages civenv
|
||||
- . ./civenv/bin/activate
|
||||
- pip3 install mypy
|
||||
- pip3 install git+https://dev.gajim.org/gajim/python-nbxmpp.git@master
|
||||
- pip3 install pylint==2.3.1
|
||||
- mypy gajim
|
||||
- scripts/dev/pylint-ci.sh --jobs=2 gajim
|
||||
- python3 setup.py test -s test.no_gui
|
||||
- deactivate
|
||||
- rm -rf civenv-master
|
||||
|
||||
run-pylint:
|
||||
run-appdata:
|
||||
stage: test
|
||||
script:
|
||||
- pylint3 --additional-builtins=_ --disable=all --enable=E0001,E0011,E0012,E0100,E0101,E0102,E0103,E0104,E0105,E0106,E0107,E0108,E0202,E0221,E0222,E0235,E0501,E0502,E0503,E0602,E0603,E0604,E0701,E0702,E1001,E1002,E1003,E1004,E1111,E1120,E1121,E1122,E1123,E1124,E1125,E1200,E1201,E1205,E1206,E1300,E1301,E1302,E1303,E1304,E1305,E1306,E1310,E1700,E1701,W0102 gajim
|
||||
- appstream-util validate data/org.gajim.Gajim.appdata.xml.in
|
||||
|
||||
run-build:
|
||||
stage: build
|
||||
|
|
@ -44,4 +54,4 @@ run-build:
|
|||
name: "gajim-default-$CI_COMMIT_REF_NAME-$CI_COMMIT_SHA"
|
||||
expire_in: 1 week
|
||||
paths:
|
||||
- gajim-default-2???-??-??.tar.gz
|
||||
- gajim-default-$CI_COMMIT_SHA.tar.gz
|
||||
|
|
|
|||
|
|
@ -1,6 +1,11 @@
|
|||
|
||||
If you want to create a fork and use an external account (Github, Google) drop us a message at the Gajim support [group chat](xmpp:gajim@conference.gajim.org?join) so we can give you the necessary rights
|
||||
|
||||
- master - Development branch
|
||||
- gajim_1.1 - Stable Gajim 1.1.x branch
|
||||
- gajim_1.0 - Stable Gajim 1.0.x branch
|
||||
- gajim_0.16 - Stable Gajim 0.16.x branch
|
||||
|
||||
If you are not familiar with Git please read the [HowTo](https://dev.gajim.org/gajim/gajim/wikis/howtogit)
|
||||
If you are not familiar with Git please read the [HowTo](https://dev.gajim.org/gajim/gajim/wikis/development/howtogit)
|
||||
|
||||
A good article regarding [good commit messages](https://chris.beams.io/posts/git-commit/)
|
||||
|
|
|
|||
41
ChangeLog
41
ChangeLog
|
|
@ -1,3 +1,44 @@
|
|||
Gajim 1.1.0 (TBA)
|
||||
|
||||
New
|
||||
|
||||
* Remove support for XEP-0091
|
||||
|
||||
Bug fixes
|
||||
|
||||
* 9322 Error when adding contact
|
||||
* 9385 Ignore invalid bookmarks
|
||||
* 9386 Discovery: Browsing nodes without identity
|
||||
* 9393 Error when parsing invalid timestamps
|
||||
* 9398 Error on jingle file transfer
|
||||
|
||||
Gajim 1.0.99.1 (07 October 2018)
|
||||
|
||||
New
|
||||
|
||||
* Implement XEP-0398
|
||||
* MUC: Set Threshold for requesting history
|
||||
* Show icon for unencrypted messages
|
||||
* Support more media players for broadcasting the current tune
|
||||
* Windows: Add a debug logging switch in preferences
|
||||
* Preferences: enable/disable dark theme
|
||||
* Preferences: enable/disable the MUC subject beeing shown on join
|
||||
* Preferences: enable/disable ascii emoji conversion
|
||||
|
||||
Bug fixes
|
||||
|
||||
* 9198 Creating new MUCs with captial letters is not possible
|
||||
* 9210 Error when clicking on new message indicator
|
||||
* 9280 Inviting users to a MUC causes error
|
||||
* 9301 Error when opening service discovery window
|
||||
* 9309 Error when clicking on a groupchat invite notification
|
||||
* 9311 Error when requesting server info
|
||||
* 9117 Windows UAC changes status not available
|
||||
* 9324 No menus/dialogs on Win7
|
||||
* 9326 IPV6 Connection problem on Win10
|
||||
* 9334 Joining big MUCs takes very long
|
||||
* 9339 Error caused by remote_control
|
||||
|
||||
Gajim 1.0.99 (19 August 2018)
|
||||
|
||||
New
|
||||
|
|
|
|||
14
README.md
14
README.md
|
|
@ -7,17 +7,16 @@
|
|||
- python3-gi
|
||||
- python3-gi-cairo
|
||||
- gir1.2-gtk-3.0 (>=3.22)
|
||||
- python3-nbxmpp
|
||||
- python3-openssl (>=0.14)
|
||||
- python3-nbxmpp (>=0.9.90.4)
|
||||
- python3-openssl (>=16.2)
|
||||
- python3-cssutils (>=1.0.2)
|
||||
- python3-keyring
|
||||
|
||||
- python3-precis-i18n
|
||||
|
||||
### Optional Runtime Requirements
|
||||
|
||||
- python3-pil (pillow) for support of webp avatars
|
||||
- python3-gnupg to enable GPG encryption
|
||||
- For zeroconf (bonjour) you need python3-dbus
|
||||
- gir1.2-avahi-0.6 for zeroconf on Linux or [pybonjour](https://dev.gajim.org/lovetox/pybonjour-python3) on Windows/macOS
|
||||
- gir1.2-gspell-1 and hunspell-LANG where lang is your locale eg. en, fr etc
|
||||
- gir1.2-secret-1 for GNOME Keyring or KDE support as password storage
|
||||
- D-Bus running to have gajim-remote working
|
||||
|
|
@ -25,7 +24,6 @@
|
|||
- gir1.2-gupnpigd-1.0 for better NAT traversing
|
||||
- gir1.2-networkmanager-1.0 for network lose detection
|
||||
- gir1.2-geoclue-2.0 for sharing your location
|
||||
- python3-idna and python3-precis-i18n for correctly parsing JIDs
|
||||
|
||||
### Compile-time Requirements
|
||||
|
||||
|
|
@ -37,7 +35,7 @@
|
|||
#### Packages
|
||||
|
||||
- [Arch](https://aur.archlinux.org/packages/gajim-git/)
|
||||
- [Debian](https://packages.debian.org/source/experimental/gajim) (tested with Debian ``testing`` and ``unstable``)
|
||||
- [Debian](https://packages.debian.org/stretch-backports/gajim)
|
||||
|
||||
#### Snapshots
|
||||
|
||||
|
|
@ -87,7 +85,7 @@ Execute gajim with --verbose
|
|||
|
||||
That is all, **enjoy**!
|
||||
|
||||
(C) 2003-2018
|
||||
(C) 2003-2019
|
||||
The Gajim Team
|
||||
[https://gajim.org](https://gajim.org)
|
||||
|
||||
|
|
|
|||
|
|
@ -18,8 +18,9 @@ clone_depth: 1
|
|||
# - ps: iex ((new-object net.webclient).DownloadString('https://raw.githubusercontent.com/appveyor/ci/master/scripts/enable-rdp.ps1'))
|
||||
|
||||
build_script:
|
||||
- C:\msys64\usr\bin\pacman -Syu --needed --noconfirm --ask=20
|
||||
- C:\msys64\usr\bin\pacman -Su --needed --noconfirm
|
||||
- del C:\Windows\System32\libssl-*.dll C:\Windows\system32\libcrypto-*.dll
|
||||
- del C:\Windows\SysWOW64\libssl-*.dll C:\Windows\SysWOW64\libcrypto-*.dll
|
||||
- C:\msys64\usr\bin\pacman -Syuu --needed --noconfirm --noprogressbar --overwrite \\*
|
||||
- ps: |
|
||||
$env:TIME_STRING=(get-date -UFormat "%Y-%m-%d").ToString()
|
||||
$env:BUILDROOT="C:\msys64\home\appveyor\gajim\win\_build_root"
|
||||
|
|
|
|||
|
|
@ -46,7 +46,7 @@ and
|
|||
.Pa THANKS ,
|
||||
for a complete list.
|
||||
.Sh COPYRIGHT
|
||||
Copyright (C) 2003-2018 Gajim Team
|
||||
Copyright (C) 2003-2019 Gajim Team
|
||||
.Pp
|
||||
.Nm
|
||||
is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; version 3 only.
|
||||
|
|
|
|||
|
|
@ -89,7 +89,7 @@ and
|
|||
.Pa THANKS ,
|
||||
for a complete list.
|
||||
.Sh COPYRIGHT
|
||||
Copyright (C) 2003-2018 Gajim Team
|
||||
Copyright (C) 2003-2019 Gajim Team
|
||||
.Pp
|
||||
.Nm
|
||||
is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; version 3 only.
|
||||
|
|
|
|||
|
|
@ -93,7 +93,7 @@ and
|
|||
with contributions and patches merged from many individuals around the world.
|
||||
See the About Dialog for a complete list.
|
||||
.Sh COPYRIGHT
|
||||
Copyright (C) 2003-2018 Gajim Team
|
||||
Copyright (C) 2003-2019 Gajim Team
|
||||
.Pp
|
||||
.Nm
|
||||
is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; version 3 only.
|
||||
|
|
|
|||
|
|
@ -7,32 +7,37 @@
|
|||
<metadata_license>CC-BY-SA-3.0</metadata_license>
|
||||
<project_license>GPL-3.0</project_license>
|
||||
<name>Gajim</name>
|
||||
<summary>A GTK+ Jabber client</summary>
|
||||
<summary>A GTK+ XMPP client</summary>
|
||||
<description>
|
||||
<p>Gajim is a chat client to be used with Jabber.org, Live Journal Talk,
|
||||
Nimbuzz, Ovi, Talkonaut, and thousands of other services
|
||||
run by companies and ISPs, and volunteers around the world.
|
||||
<p>Gajim aims to be an easy to use and fully-featured XMPP client.
|
||||
With Gajim you can chat through various XMPP services of your choice
|
||||
(e.g. Jabber.org) as well as transports (e.g. Facebook, IRC).
|
||||
</p>
|
||||
<p>If you have a few accounts on different servers, if you want to be in
|
||||
contact with your friends and family all the time, then Gajim is for you.
|
||||
<p>Just chat with your friends or family, easily share pictures and
|
||||
thoughts or discuss the news with your groups.
|
||||
</p>
|
||||
<p>Gajim integrates well with your other devices: just chat and see what's
|
||||
been said on your mobile device.
|
||||
</p>
|
||||
<p>Features:</p>
|
||||
<ul>
|
||||
<li>Tabbed chat window and single window modes</li>
|
||||
<li>Group chat support (with Multi-User Chat protocol), invitation, chat to group chat transformation</li>
|
||||
<li>Emoticons, avatars, PEP (user activity, mood and tune)</li>
|
||||
<li>Audio / video conferences</li>
|
||||
<li>File transfer, room bookmarks</li>
|
||||
<li>Metacontacts support</li>
|
||||
<li>Trayicon, speller, extended chat history functionalities</li>
|
||||
<li>TLS, GPG and End-To-End encryption support</li>
|
||||
<li>Transport registration support</li>
|
||||
<li>Service discovery including nodes, user search</li>
|
||||
<li>Wikipedia, dictionary and search engine lookup</li>
|
||||
<li>Multiple accounts support</li>
|
||||
<li>XML console interface</li>
|
||||
<li>Link local (bonjour / zeroconf), BOSH</li>
|
||||
<li>Other features via plugins </li>
|
||||
<li>Never miss a message, keep all your chat clients synchronized</li>
|
||||
<li>Invite friends to group chats or join one</li>
|
||||
<li>Easily send pictures, videos or other files to friends and groups</li>
|
||||
<li>Chat securely with End-to-End encryption via OMEMO or PGP</li>
|
||||
<li>Use your favorite emoticons, set your own profile picture</li>
|
||||
<li>Keep and manage all your chat history</li>
|
||||
<li>Organize your chats with tabs</li>
|
||||
<li>Automatic spell-checking for your messages</li>
|
||||
<li>Connect to other Messengers via Transports (Facebook, IRC, ...)</li>
|
||||
<li>Lookup things on Wikipedia, dictionaries or other search engines directly from the chat window</li>
|
||||
<li>Set your activity, tune, and mood to show your friends how you are feeling</li>
|
||||
<li>Support for multiple accounts</li>
|
||||
<li>Group multiple contacts from one friend to a single Meta-Contact</li>
|
||||
<li>XML console to see what's happening on the protocol layer</li>
|
||||
<li>Serverless messaging (Bonjour/Zeroconf), BOSH</li>
|
||||
<li>Support for service discovery including nodes and search for users</li>
|
||||
<li>Even more features via plugins</li>
|
||||
</ul>
|
||||
</description>
|
||||
<categories>
|
||||
|
|
@ -43,7 +48,7 @@
|
|||
</categories>
|
||||
<screenshots>
|
||||
<screenshot type="default">
|
||||
<image>https://gajim.org/imgs/gajim_roster.png</image>
|
||||
<image>https://gajim.org/imgs/gajim_single_window_mode.png</image>
|
||||
<caption>Roster, list of contacts</caption>
|
||||
</screenshot>
|
||||
<screenshot>
|
||||
|
|
@ -69,7 +74,7 @@
|
|||
<url type="homepage">https://gajim.org/</url>
|
||||
<url type="bugtracker">https://dev.gajim.org/gajim/gajim</url>
|
||||
<url type="faq">https://dev.gajim.org/gajim/gajim/wikis/help/gajimfaq</url>
|
||||
<url type="help">https://dev.gajim.org/gajim/gajim/wikis/help/home</url>
|
||||
<url type="help">https://dev.gajim.org/gajim/gajim/wikis/help/Help</url>
|
||||
<url type="donation">https://gajim.org/dev.php#donate</url>
|
||||
<url type="translate">https://dev.gajim.org/gajim/gajim/wikis/development/DevTranslate</url>
|
||||
<translation type="gettext">gajim</translation>
|
||||
|
|
|
|||
|
|
@ -13,3 +13,14 @@ StartupWMClass=Gajim
|
|||
Terminal=false
|
||||
Type=Application
|
||||
MimeType=x-scheme-handler/xmpp;
|
||||
Actions=StartChat;ShowNextPendingEvent;
|
||||
|
||||
[Desktop Action StartChat]
|
||||
Exec=gajim --start-chat
|
||||
Name=Start a new chat
|
||||
Icon=org.gajim.Gajim
|
||||
|
||||
[Desktop Action ShowNextPendingEvent]
|
||||
Exec=gajim --show-next-pending-event
|
||||
Name=Show next pending event
|
||||
Icon=org.gajim.Gajim
|
||||
|
|
|
|||
|
|
@ -1,29 +0,0 @@
|
|||
[epydoc]
|
||||
|
||||
# Information about the project.
|
||||
name: Gajim
|
||||
url: http://gajim.org
|
||||
|
||||
verbosity: 3
|
||||
imports: yes
|
||||
redundant-details: yes
|
||||
docformat: restructuredtext
|
||||
# top: gajim
|
||||
|
||||
# The list of modules to document. Modules can be named using
|
||||
# dotted names, module filenames, or package directory names.
|
||||
# This option may be repeated.
|
||||
modules: gajim/* test/*
|
||||
|
||||
# Write html output to the directory "apidocs"
|
||||
#output: pdf
|
||||
output: html
|
||||
target: doc/apidocs/
|
||||
|
||||
# Include all automatically generated graphs. These graphs are
|
||||
# generated using Graphviz dot.
|
||||
graph: all
|
||||
dotpath: /usr/bin/dot
|
||||
graph-font: Sans
|
||||
graph-font-size: 10
|
||||
|
||||
|
|
@ -23,9 +23,9 @@ Replace install path `~/Gajim/gajim_flatpak` with an install path of your choice
|
|||
*Note: Remove `--user` if you want a system-wide installation.*
|
||||
|
||||
1. `flatpak --user remote-add --from gnome https://sdk.gnome.org/gnome.flatpakrepo`
|
||||
2. `flatpak --user install gnome org.gnome.Platform//3.28`
|
||||
3. `flatpak --user install gnome org.gnome.Sdk//3.28`
|
||||
4. `flatpak-builder --repo=gajim_flatpak_repo ~/Gajim/gajim_flatpak ~/Gajim/org.gajim.Gajim.json`
|
||||
2. `flatpak --user install gnome org.gnome.Platform//3.30`
|
||||
3. `flatpak --user install gnome org.gnome.Sdk//3.30`
|
||||
4. `flatpak-builder --repo=gajim_flatpak_repo ~/Gajim/gajim_flatpak ~/Gajim/flatpak/org.gajim.Gajim.yaml`
|
||||
5. `flatpak --user remote-add --no-gpg-verify gajim_flatpak_repo gajim_flatpak_repo`
|
||||
6. `flatpak --user install gajim_flatpak_repo org.gajim.Gajim`
|
||||
7. `flatpak run org.gajim.Gajim`
|
||||
|
|
@ -51,7 +51,7 @@ In this example, we use `git` to update the repository. You can also download th
|
|||
|
||||
### Install and update Gajim
|
||||
|
||||
1. `flatpak-builder --repo=gajim_flatpak_repo ~/Gajim/gajim_flatpak ~/Gajim/org.gajim.Gajim.json`
|
||||
1. `flatpak-builder --repo=gajim_flatpak_repo ~/Gajim/gajim_flatpak ~/Gajim/flatpak/org.gajim.Gajim.yaml`
|
||||
2. `flatpak --user update`
|
||||
3. `flatpak run org.gajim.Gajim`
|
||||
|
||||
|
|
|
|||
199
flatpak/org.gajim.Gajim.yaml
Normal file
199
flatpak/org.gajim.Gajim.yaml
Normal file
|
|
@ -0,0 +1,199 @@
|
|||
app-id: org.gajim.Gajim
|
||||
runtime: org.gnome.Platform
|
||||
runtime-version: 3.32
|
||||
sdk: org.gnome.Sdk
|
||||
command: gajim
|
||||
tags: nightly
|
||||
desktop-file-name-prefix: '(Nightly) '
|
||||
finish-args:
|
||||
- --share=ipc
|
||||
- --share=network
|
||||
- --socket=fallback-x11
|
||||
- --socket=wayland
|
||||
- --socket=pulseaudio
|
||||
- --system-talk-name=org.freedesktop.GeoClue2
|
||||
- --system-talk-name=org.freedesktop.login1
|
||||
- --talk-name=org.freedesktop.Notifications
|
||||
- --talk-name=org.gnome.ScreenSaver
|
||||
- --talk-name=org.mpris.MediaPlayer2.*
|
||||
# Keyring
|
||||
- --talk-name=org.freedesktop.secrets
|
||||
# dconf
|
||||
- --filesystem=xdg-run/dconf
|
||||
- --filesystem=~/.config/dconf:ro
|
||||
- --talk-name=ca.desrt.dconf
|
||||
- --env=DCONF_USER_CONFIG_DIR=.config/dconf
|
||||
# GnuPG
|
||||
- --filesystem=~/.gnupg
|
||||
# extensions
|
||||
- --env=PYTHONPATH=/app/plugins/lib/python3.7/site-packages
|
||||
|
||||
add-extensions:
|
||||
org.gajim.Gajim.Plugin:
|
||||
directory: plugins
|
||||
merge-dirs: lib/python3.7/site-packages
|
||||
subdirectories: true
|
||||
no-autodownload: true
|
||||
autodelete: true
|
||||
|
||||
cleanup:
|
||||
- '/bin/easy*'
|
||||
- /include
|
||||
- /lib/debug
|
||||
- /lib/pkgconfig
|
||||
- /lib/python2.7
|
||||
- /share/aclocal
|
||||
- /share/doc
|
||||
- /share/gtk-doc
|
||||
- /share/man
|
||||
- '*.a'
|
||||
- '*.la'
|
||||
|
||||
modules:
|
||||
- name: python3-pycparser
|
||||
buildsystem: simple
|
||||
build-commands:
|
||||
- pip3 install --prefix=/app .
|
||||
sources:
|
||||
- type: archive
|
||||
url: https://files.pythonhosted.org/packages/68/9e/49196946aee219aead1290e00d1e7fdeab8567783e83e1b9ab5585e6206a/pycparser-2.19.tar.gz
|
||||
sha256: a988718abfad80b6b157acce7bf130a30876d27603738ac39f140993246b25b3
|
||||
|
||||
- name: python3-cffi
|
||||
buildsystem: simple
|
||||
build-commands:
|
||||
- pip3 install --prefix=/app .
|
||||
sources:
|
||||
- type: archive
|
||||
url: https://files.pythonhosted.org/packages/64/7c/27367b38e6cc3e1f49f193deb761fe75cda9f95da37b67b422e62281fcac/cffi-1.12.2.tar.gz
|
||||
sha256: e113878a446c6228669144ae8a56e268c91b7f1fafae927adc4879d9849e0ea7
|
||||
|
||||
- name: python3-asn1crypto
|
||||
buildsystem: simple
|
||||
build-commands:
|
||||
- pip3 install --prefix=/app asn1crypto-0.24.0-py2.py3-none-any.whl
|
||||
sources:
|
||||
- type: file
|
||||
url: https://pypi.python.org/packages/ea/cd/35485615f45f30a510576f1a56d1e0a7ad7bd8ab5ed7cdc600ef7cd06222/asn1crypto-0.24.0-py2.py3-none-any.whl
|
||||
sha256: 2f1adbb7546ed199e3c90ef23ec95c5cf3585bac7d11fb7eb562a3fe89c64e87
|
||||
|
||||
- name: python3-idna
|
||||
buildsystem: simple
|
||||
build-commands:
|
||||
- pip3 install --prefix=/app idna-2.8-py2.py3-none-any.whl
|
||||
sources:
|
||||
- type: file
|
||||
url: https://files.pythonhosted.org/packages/14/2c/cd551d81dbe15200be1cf41cd03869a46fe7226e7450af7a6545bfc474c9/idna-2.8-py2.py3-none-any.whl
|
||||
sha256: ea8b7f6188e6fa117537c3df7da9fc686d485087abf6ac197f9c46432f7e4a3c
|
||||
|
||||
- name: python3-cryptography
|
||||
buildsystem: simple
|
||||
build-commands:
|
||||
- python3 setup.py install --prefix=/app
|
||||
sources:
|
||||
- type: archive
|
||||
url: https://files.pythonhosted.org/packages/07/ca/bc827c5e55918ad223d59d299fff92f3563476c3b00d0a9157d9c0217449/cryptography-2.6.1.tar.gz
|
||||
sha256: 26c821cbeb683facb966045e2064303029d572a87ee69ca5a1bf54bf55f93ca6
|
||||
|
||||
- name: python3-pyopenssl
|
||||
buildsystem: simple
|
||||
build-commands:
|
||||
- pip3 install --prefix=/app pyOpenSSL-19.0.0-py2.py3-none-any.whl
|
||||
sources:
|
||||
- type: file
|
||||
url: https://files.pythonhosted.org/packages/01/c8/ceb170d81bd3941cbeb9940fc6cc2ef2ca4288d0ca8929ea4db5905d904d/pyOpenSSL-19.0.0-py2.py3-none-any.whl
|
||||
sha256: c727930ad54b10fc157015014b666f2d8b41f70c0d03e83ab67624fd3dd5d1e6
|
||||
|
||||
- name: python3-dbus-python
|
||||
build-options:
|
||||
env:
|
||||
PYTHON_VERSION: '3'
|
||||
sources:
|
||||
- type: archive
|
||||
url: https://files.pythonhosted.org/packages/3f/e7/4edb582d1ffd5ac3c84188deea32e960b5c8c0fe1da56ce70224f85ce542/dbus-python-1.2.8.tar.gz
|
||||
sha256: abf12bbb765e300bf8e2a1b2f32f85949eab06998dbda127952c31cb63957b6f
|
||||
|
||||
- name: python3-jeepney
|
||||
buildsystem: simple
|
||||
build-commands:
|
||||
- pip3 install --prefix=/app jeepney-0.4-py3-none-any.whl
|
||||
sources:
|
||||
- type: file
|
||||
url: https://files.pythonhosted.org/packages/2b/f7/ff23b9b59534f501d47c327576aadda59da5b83d76ff837e6075bc325b9f/jeepney-0.4-py3-none-any.whl
|
||||
sha256: f6f8b1428403b4afad04b6b82f9ab9fc426c253d7504c9031c41712a2c01dc74
|
||||
|
||||
- name: python3-secretstorage
|
||||
buildsystem: simple
|
||||
build-commands:
|
||||
- pip3 install --prefix=/app SecretStorage-3.1.1-py3-none-any.whl
|
||||
sources:
|
||||
- type: file
|
||||
url: https://files.pythonhosted.org/packages/82/59/cb226752e20d83598d7fdcabd7819570b0329a61db07cfbdd21b2ef546e3/SecretStorage-3.1.1-py3-none-any.whl
|
||||
sha256: 7a119fb52a88e398dbb22a4b3eb39b779bfbace7e4153b7bc6e5954d86282a8a
|
||||
|
||||
- name: python3-entrypoints
|
||||
buildsystem: simple
|
||||
build-commands:
|
||||
- pip3 install --prefix=/app entrypoints-0.3-py2.py3-none-any.whl
|
||||
sources:
|
||||
- type: file
|
||||
url: https://files.pythonhosted.org/packages/ac/c6/44694103f8c221443ee6b0041f69e2740d89a25641e62fb4f2ee568f2f9c/entrypoints-0.3-py2.py3-none-any.whl
|
||||
sha256: 589f874b313739ad35be6e0cd7efde2a4e9b6fea91edcc34e58ecbb8dbe56d19
|
||||
|
||||
- name: python3-keyring
|
||||
buildsystem: simple
|
||||
build-commands:
|
||||
- pip3 install --prefix=/app keyring-18.0.0-py2.py3-none-any.whl
|
||||
cleanup:
|
||||
- /bin
|
||||
sources:
|
||||
- type: file
|
||||
url: https://files.pythonhosted.org/packages/a1/28/0058032477bfdf2003e605d175629963759220661615443e20711446bfa7/keyring-18.0.0-py2.py3-none-any.whl
|
||||
sha256: ca33f5ccc542b9ffaa196ee9a33488069e5e7eac77d5b81969f8a3ce74d0230c
|
||||
|
||||
- name: python3-cssutils
|
||||
buildsystem: simple
|
||||
build-commands:
|
||||
- pip3 install --prefix=/app cssutils-1.0.2-py3-none-any.whl
|
||||
cleanup:
|
||||
- /bin
|
||||
sources:
|
||||
- type: file
|
||||
url: https://files.pythonhosted.org/packages/6b/15/a9fb9010f58d1c55dd0b7779db2334feb9a572d407024f39a60f44293861/cssutils-1.0.2-py3-none-any.whl
|
||||
sha256: c74dbe19c92f5052774eadb15136263548dd013250f1ed1027988e7fef125c8d
|
||||
|
||||
- name: python3-precis_i18n
|
||||
buildsystem: simple
|
||||
build-commands:
|
||||
- pip3 install --prefix=/app precis_i18n-1.0.0-py3-none-any.whl
|
||||
sources:
|
||||
- type: file
|
||||
url: https://files.pythonhosted.org/packages/08/9d/d32f1b7c6d280c82a786b629ba89bd9e3e8409b9e23a7309f76a78e46971/precis_i18n-1.0.0-py3-none-any.whl
|
||||
sha256: f6942bbffec698d0d7e30c42f589f3f6da13e723d047997d0761a41facf2986f
|
||||
|
||||
- name: gspell
|
||||
cleanup:
|
||||
- /bin
|
||||
sources:
|
||||
- type: archive
|
||||
url: https://download.gnome.org/sources/gspell/1.8/gspell-1.8.1.tar.xz
|
||||
sha256: 819a1d23c7603000e73f5e738bdd284342e0cd345fb0c7650999c31ec741bbe5
|
||||
|
||||
- name: python3-nbxmpp
|
||||
buildsystem: simple
|
||||
build-commands:
|
||||
- pip3 install --prefix=/app .
|
||||
sources:
|
||||
- type: git
|
||||
url: https://dev.gajim.org/gajim/python-nbxmpp.git
|
||||
|
||||
- name: gajim
|
||||
buildsystem: simple
|
||||
build-commands:
|
||||
- pip3 install --prefix=/app .
|
||||
- touch /app/share/run-as-flatpak
|
||||
sources:
|
||||
- type: git
|
||||
url: https://dev.gajim.org/gajim/gajim.git
|
||||
post-install:
|
||||
- install -d /app/plugins
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
import os
|
||||
import subprocess
|
||||
|
||||
__version__ = "1.0.3"
|
||||
__version__ = "1.1.92"
|
||||
|
||||
IS_FLATPAK = False
|
||||
if os.path.exists('/app/share/run-as-flatpak'):
|
||||
|
|
|
|||
|
|
@ -1,793 +0,0 @@
|
|||
from functools import partial
|
||||
|
||||
from gi.repository import Gtk, GLib, Gdk
|
||||
|
||||
from gajim.common import app
|
||||
from gajim import gtkgui_helpers
|
||||
from gajim import gui_menu_builder
|
||||
from gajim.common import passwords
|
||||
from gajim import config
|
||||
from gajim.common import helpers
|
||||
from gajim.common import ged
|
||||
from gajim.common.connection import Connection
|
||||
from gajim.common.zeroconf.connection_zeroconf import ConnectionZeroconf
|
||||
from gajim.options_dialog import OptionsDialog, OptionsBox
|
||||
from gajim.common.const import Option, OptionKind, OptionType
|
||||
from gajim.gtk import ConfirmationDialog
|
||||
from gajim.gtk import YesNoDialog
|
||||
|
||||
|
||||
class AccountsWindow(Gtk.ApplicationWindow):
|
||||
def __init__(self):
|
||||
Gtk.ApplicationWindow.__init__(self)
|
||||
self.set_application(app.app)
|
||||
self.set_position(Gtk.WindowPosition.CENTER)
|
||||
self.set_show_menubar(False)
|
||||
self.set_name('AccountsWindow')
|
||||
self.set_default_size(700, 550)
|
||||
self.set_resizable(False)
|
||||
self.set_title(_('Accounts'))
|
||||
self.need_relogin = {}
|
||||
|
||||
glade_objects = ['stack', 'box', 'account_list']
|
||||
self.builder = gtkgui_helpers.get_gtk_builder('accounts_window.ui')
|
||||
for obj in glade_objects:
|
||||
setattr(self, obj, self.builder.get_object(obj))
|
||||
|
||||
self.account_list.add(Preferences(self))
|
||||
account_item = AddAccount()
|
||||
self.account_list.add(account_item)
|
||||
account_item.set_activatable()
|
||||
|
||||
accounts = app.config.get_per('accounts')
|
||||
accounts.sort()
|
||||
for account in accounts:
|
||||
self.need_relogin[account] = self.get_relogin_options(account)
|
||||
account_item = Account(account, self)
|
||||
self.account_list.add(account_item)
|
||||
account_item.set_activatable()
|
||||
|
||||
self.add(self.box)
|
||||
self.builder.connect_signals(self)
|
||||
|
||||
self.connect('destroy', self.on_destroy)
|
||||
self.connect('key-press-event', self.on_key_press)
|
||||
|
||||
self._activate_preferences_page()
|
||||
self.show_all()
|
||||
|
||||
app.ged.register_event_handler(
|
||||
'our-show', ged.GUI2, self._nec_our_status)
|
||||
|
||||
def _nec_our_status(self, event):
|
||||
self.update_accounts()
|
||||
|
||||
def _activate_preferences_page(self):
|
||||
row = self.account_list.get_row_at_index(0)
|
||||
self.account_list.select_row(row)
|
||||
self.account_list.emit('row-activated', row)
|
||||
|
||||
def on_key_press(self, widget, event):
|
||||
if event.keyval == Gdk.KEY_Escape:
|
||||
self.destroy()
|
||||
|
||||
def on_destroy(self, *args):
|
||||
self.check_relogin()
|
||||
app.ged.remove_event_handler(
|
||||
'our-show', ged.GUI2, self._nec_our_status)
|
||||
|
||||
def on_child_visible(self, stack, *args):
|
||||
page = stack.get_visible_child_name()
|
||||
if page is None:
|
||||
return
|
||||
if page == 'account':
|
||||
self.check_relogin()
|
||||
|
||||
def update_accounts(self):
|
||||
for row in self.account_list.get_children():
|
||||
row.get_child().update()
|
||||
|
||||
@staticmethod
|
||||
def on_row_activated(listbox, row):
|
||||
row.get_child().on_row_activated()
|
||||
|
||||
def remove_all_pages(self):
|
||||
for page in self.stack.get_children():
|
||||
self.stack.remove(page)
|
||||
|
||||
def set_page(self, page, name):
|
||||
self.remove_all_pages()
|
||||
self.stack.add_named(page, name)
|
||||
page.update()
|
||||
page.show_all()
|
||||
self.stack.set_visible_child(page)
|
||||
|
||||
def update_proxy_list(self):
|
||||
page = self.stack.get_child_by_name('connection')
|
||||
if page is None:
|
||||
return
|
||||
page.listbox.get_option('proxy').update_values()
|
||||
|
||||
def check_relogin(self):
|
||||
for account in self.need_relogin:
|
||||
options = self.get_relogin_options(account)
|
||||
active = app.config.get_per('accounts', account, 'active')
|
||||
if options != self.need_relogin[account]:
|
||||
self.need_relogin[account] = options
|
||||
if active:
|
||||
self.relog(account)
|
||||
break
|
||||
|
||||
def relog(self, account):
|
||||
if app.connections[account].connected == 0:
|
||||
return
|
||||
|
||||
if account == app.ZEROCONF_ACC_NAME:
|
||||
app.connections[app.ZEROCONF_ACC_NAME].update_details()
|
||||
return
|
||||
|
||||
def login(account, show_before, status_before):
|
||||
"""
|
||||
Login with previous status
|
||||
"""
|
||||
# first make sure connection is really closed,
|
||||
# 0.5 may not be enough
|
||||
app.connections[account].disconnect(True)
|
||||
app.interface.roster.send_status(
|
||||
account, show_before, status_before)
|
||||
|
||||
def relog(account):
|
||||
show_before = app.SHOW_LIST[app.connections[account].connected]
|
||||
status_before = app.connections[account].status
|
||||
app.interface.roster.send_status(
|
||||
account, 'offline', _('Be right back.'))
|
||||
GLib.timeout_add(500, login, account, show_before, status_before)
|
||||
|
||||
YesNoDialog(
|
||||
_('Relogin now?'),
|
||||
_('If you want all the changes to apply instantly, '
|
||||
'you must relogin.'),
|
||||
transient_for=self,
|
||||
on_response_yes=lambda *args: relog(account))
|
||||
|
||||
@staticmethod
|
||||
def get_relogin_options(account):
|
||||
if account == app.ZEROCONF_ACC_NAME:
|
||||
options = ['zeroconf_first_name', 'zeroconf_last_name',
|
||||
'zeroconf_jabber_id', 'zeroconf_email', 'keyid']
|
||||
else:
|
||||
options = ['client_cert', 'proxy', 'resource',
|
||||
'use_custom_host', 'custom_host', 'custom_port',
|
||||
'keyid']
|
||||
|
||||
values = []
|
||||
for option in options:
|
||||
values.append(app.config.get_per('accounts', account, option))
|
||||
return values
|
||||
|
||||
def on_remove_account(self, button, account):
|
||||
if app.events.get_events(account):
|
||||
app.interface.raise_dialog('unread-events-on-remove-account')
|
||||
return
|
||||
|
||||
if app.config.get_per('accounts', account, 'is_zeroconf'):
|
||||
# Should never happen as button is insensitive
|
||||
return
|
||||
|
||||
win_opened = False
|
||||
if app.interface.msg_win_mgr.get_controls(acct=account):
|
||||
win_opened = True
|
||||
elif account in app.interface.instances:
|
||||
for key in app.interface.instances[account]:
|
||||
if (app.interface.instances[account][key] and
|
||||
key != 'remove_account'):
|
||||
win_opened = True
|
||||
break
|
||||
|
||||
# Detect if we have opened windows for this account
|
||||
|
||||
def remove(account):
|
||||
if (account in app.interface.instances and
|
||||
'remove_account' in app.interface.instances[account]):
|
||||
dialog = app.interface.instances[account]['remove_account']
|
||||
dialog.window.present()
|
||||
else:
|
||||
if account not in app.interface.instances:
|
||||
app.interface.instances[account] = {}
|
||||
app.interface.instances[account]['remove_account'] = \
|
||||
config.RemoveAccountWindow(account)
|
||||
if win_opened:
|
||||
ConfirmationDialog(
|
||||
_('You have opened chat in account %s') % account,
|
||||
_('All chat and groupchat windows will be closed. '
|
||||
'Do you want to continue?'),
|
||||
on_response_ok=(remove, account),
|
||||
transient_for=self)
|
||||
else:
|
||||
remove(account)
|
||||
|
||||
def remove_account(self, account):
|
||||
for row in self.account_list.get_children():
|
||||
if row.get_child().account == account:
|
||||
self.account_list.remove(row)
|
||||
del self.need_relogin[account]
|
||||
break
|
||||
self._activate_preferences_page()
|
||||
|
||||
def add_account(self, account):
|
||||
account_item = Account(account, self)
|
||||
self.account_list.add(account_item)
|
||||
account_item.set_activatable()
|
||||
self.account_list.show_all()
|
||||
self.stack.show_all()
|
||||
self.need_relogin[account] = self.get_relogin_options(account)
|
||||
|
||||
def select_account(self, account):
|
||||
for row in self.account_list.get_children():
|
||||
if row.get_child().account == account:
|
||||
self.account_list.select_row(row)
|
||||
self.account_list.emit('row-activated', row)
|
||||
break
|
||||
|
||||
@staticmethod
|
||||
def enable_account(account):
|
||||
if account == app.ZEROCONF_ACC_NAME:
|
||||
app.connections[account] = ConnectionZeroconf(account)
|
||||
else:
|
||||
app.connections[account] = Connection(account)
|
||||
|
||||
# update variables
|
||||
app.interface.instances[account] = {
|
||||
'infos': {}, 'disco': {}, 'gc_config': {}, 'search': {},
|
||||
'online_dialog': {}, 'sub_request': {}}
|
||||
app.interface.minimized_controls[account] = {}
|
||||
app.connections[account].connected = 0
|
||||
app.groups[account] = {}
|
||||
app.contacts.add_account(account)
|
||||
app.gc_connected[account] = {}
|
||||
app.automatic_rooms[account] = {}
|
||||
app.newly_added[account] = []
|
||||
app.to_be_removed[account] = []
|
||||
if account == app.ZEROCONF_ACC_NAME:
|
||||
app.nicks[account] = app.ZEROCONF_ACC_NAME
|
||||
else:
|
||||
app.nicks[account] = app.config.get_per(
|
||||
'accounts', account, 'name')
|
||||
app.block_signed_in_notifications[account] = True
|
||||
app.sleeper_state[account] = 'off'
|
||||
app.encrypted_chats[account] = []
|
||||
app.last_message_time[account] = {}
|
||||
app.status_before_autoaway[account] = ''
|
||||
app.transport_avatar[account] = {}
|
||||
app.gajim_optional_features[account] = []
|
||||
app.caps_hash[account] = ''
|
||||
helpers.update_optional_features(account)
|
||||
# refresh roster
|
||||
if len(app.connections) >= 2:
|
||||
# Do not merge accounts if only one exists
|
||||
app.interface.roster.regroup = app.config.get('mergeaccounts')
|
||||
else:
|
||||
app.interface.roster.regroup = False
|
||||
app.interface.roster.setup_and_draw_roster()
|
||||
gui_menu_builder.build_accounts_menu()
|
||||
|
||||
@staticmethod
|
||||
def disable_account(account):
|
||||
app.interface.roster.close_all(account)
|
||||
if account == app.ZEROCONF_ACC_NAME:
|
||||
app.connections[account].disable_account()
|
||||
app.connections[account].cleanup()
|
||||
del app.connections[account]
|
||||
del app.interface.instances[account]
|
||||
del app.interface.minimized_controls[account]
|
||||
del app.nicks[account]
|
||||
del app.block_signed_in_notifications[account]
|
||||
del app.groups[account]
|
||||
app.contacts.remove_account(account)
|
||||
del app.gc_connected[account]
|
||||
del app.automatic_rooms[account]
|
||||
del app.to_be_removed[account]
|
||||
del app.newly_added[account]
|
||||
del app.sleeper_state[account]
|
||||
del app.encrypted_chats[account]
|
||||
del app.last_message_time[account]
|
||||
del app.status_before_autoaway[account]
|
||||
del app.transport_avatar[account]
|
||||
del app.gajim_optional_features[account]
|
||||
del app.caps_hash[account]
|
||||
if len(app.connections) >= 2:
|
||||
# Do not merge accounts if only one exists
|
||||
app.interface.roster.regroup = app.config.get('mergeaccounts')
|
||||
else:
|
||||
app.interface.roster.regroup = False
|
||||
app.config.set_per(
|
||||
'accounts', account, 'roster_version', '')
|
||||
app.interface.roster.setup_and_draw_roster()
|
||||
gui_menu_builder.build_accounts_menu()
|
||||
|
||||
|
||||
class AddAccount(Gtk.Box):
|
||||
def __init__(self):
|
||||
Gtk.Box.__init__(self, orientation=Gtk.Orientation.HORIZONTAL,
|
||||
spacing=12)
|
||||
|
||||
self.account = None
|
||||
|
||||
self.label = Gtk.Label(label=_('Add Account…'))
|
||||
self.label.set_halign(Gtk.Align.START)
|
||||
self.label.set_hexpand(True)
|
||||
|
||||
self.image = Gtk.Image.new_from_icon_name(
|
||||
'list-add-symbolic', Gtk.IconSize.MENU)
|
||||
|
||||
self.add(self.image)
|
||||
self.add(self.label)
|
||||
|
||||
def set_activatable(self):
|
||||
self.get_parent().set_selectable(False)
|
||||
|
||||
def on_row_activated(self):
|
||||
app.app.activate_action('add-account')
|
||||
|
||||
def update(self):
|
||||
pass
|
||||
|
||||
|
||||
class Preferences(Gtk.Box):
|
||||
def __init__(self, parent):
|
||||
Gtk.Box.__init__(self, orientation=Gtk.Orientation.HORIZONTAL,
|
||||
spacing=12)
|
||||
|
||||
self.options = PreferencesPage()
|
||||
self.parent = parent
|
||||
self.account = None
|
||||
|
||||
self.label = Gtk.Label(label=_('Preferences'))
|
||||
self.label.set_halign(Gtk.Align.START)
|
||||
self.label.set_hexpand(True)
|
||||
|
||||
self.image = Gtk.Image.new_from_icon_name(
|
||||
'system-run-symbolic', Gtk.IconSize.MENU)
|
||||
|
||||
self.add(self.image)
|
||||
self.add(self.label)
|
||||
|
||||
def set_activatable(self):
|
||||
pass
|
||||
|
||||
def on_row_activated(self):
|
||||
self.options.update_states()
|
||||
self.parent.set_page(self.options, 'pref')
|
||||
|
||||
def update(self):
|
||||
pass
|
||||
|
||||
|
||||
class Account(Gtk.Box):
|
||||
def __init__(self, account, parent):
|
||||
Gtk.Box.__init__(self, orientation=Gtk.Orientation.HORIZONTAL,
|
||||
spacing=12)
|
||||
self.account = account
|
||||
if account == app.ZEROCONF_ACC_NAME:
|
||||
self.options = ZeroConfPage(account, parent)
|
||||
else:
|
||||
self.options = AccountPage(account, parent)
|
||||
self.parent = parent
|
||||
|
||||
account_label = app.config.get_per('accounts', account, 'account_label')
|
||||
self.label = Gtk.Label(label=account_label or account)
|
||||
self.label.set_halign(Gtk.Align.START)
|
||||
self.label.set_hexpand(True)
|
||||
|
||||
self.image = Gtk.Image()
|
||||
self._update_image()
|
||||
|
||||
self.add(self.image)
|
||||
self.add(self.label)
|
||||
|
||||
def set_activatable(self):
|
||||
if self.account == app.ZEROCONF_ACC_NAME:
|
||||
zeroconf = app.is_installed('ZEROCONF')
|
||||
self.get_parent().set_activatable(zeroconf)
|
||||
self.get_parent().set_sensitive(zeroconf)
|
||||
if not zeroconf:
|
||||
self.get_parent().set_tooltip_text(
|
||||
_('Please check if Avahi or Bonjour is installed.'))
|
||||
|
||||
def on_row_activated(self):
|
||||
self.options.update_states()
|
||||
self.parent.set_page(self.options, 'account')
|
||||
|
||||
def update(self):
|
||||
account_label = app.config.get_per(
|
||||
'accounts', self.account, 'account_label')
|
||||
self.label.set_text(account_label or self.account)
|
||||
self._update_image()
|
||||
|
||||
def _update_image(self):
|
||||
show = helpers.get_current_show(self.account)
|
||||
icon = gtkgui_helpers.get_iconset_name_for(show)
|
||||
self.image.set_from_icon_name(icon, Gtk.IconSize.MENU)
|
||||
|
||||
|
||||
class GenericOptionPage(Gtk.Box):
|
||||
def __init__(self, account, parent, options):
|
||||
Gtk.Box.__init__(self, orientation=Gtk.Orientation.VERTICAL, spacing=12)
|
||||
self.account = account
|
||||
self.parent = parent
|
||||
|
||||
button = Gtk.Button.new_from_icon_name(
|
||||
'go-previous-symbolic', Gtk.IconSize.MENU)
|
||||
button.set_halign(Gtk.Align.START)
|
||||
button.connect('clicked', self._on_back_button)
|
||||
if not isinstance(self, (AccountPage, PreferencesPage, ZeroConfPage)):
|
||||
self.pack_start(button, False, True, 0)
|
||||
|
||||
self.listbox = OptionsBox(account)
|
||||
self.listbox.set_selection_mode(Gtk.SelectionMode.NONE)
|
||||
self.listbox.set_vexpand(False)
|
||||
self.listbox.set_valign(Gtk.Align.END)
|
||||
|
||||
for option in options:
|
||||
self.listbox.add_option(option)
|
||||
self.listbox.update_states()
|
||||
|
||||
self.pack_end(self.listbox, True, True, 0)
|
||||
|
||||
self.listbox.connect('row-activated', self.on_row_activated)
|
||||
|
||||
def _on_back_button(self, *args):
|
||||
account_window = self.get_toplevel()
|
||||
child = account_window.stack.get_visible_child()
|
||||
account_window.remove_all_pages()
|
||||
account_window.stack.add_named(child.parent, 'account')
|
||||
account_window.stack.set_visible_child_name('account')
|
||||
|
||||
def update_states(self):
|
||||
self.listbox.update_states()
|
||||
|
||||
def on_row_activated(self, listbox, row):
|
||||
row.get_child().on_row_activated()
|
||||
|
||||
def set_entry_text(self, toggle, update=False):
|
||||
account_label = app.config.get_per(
|
||||
'accounts', self.account, 'account_label')
|
||||
if update:
|
||||
self.entry.set_text(account_label or self.account)
|
||||
return
|
||||
if toggle.get_active():
|
||||
self.entry.set_sensitive(True)
|
||||
self.entry.grab_focus()
|
||||
else:
|
||||
self.entry.set_sensitive(False)
|
||||
value = self.entry.get_text()
|
||||
if not value:
|
||||
value = account_label or self.account
|
||||
app.config.set_per('accounts', self.account,
|
||||
'account_label', value or self.account)
|
||||
if app.config.get_per('accounts', self.account, 'active'):
|
||||
app.interface.roster.draw_account(self.account)
|
||||
gui_menu_builder.build_accounts_menu()
|
||||
|
||||
def update(self):
|
||||
pass
|
||||
|
||||
def set_page(self, options, name):
|
||||
options.update_states()
|
||||
self.get_toplevel().set_page(options, name)
|
||||
|
||||
def _add_top_buttons(self, parent):
|
||||
# This adds the Account enable switch and the back button
|
||||
box = Gtk.Box()
|
||||
box.set_hexpand(True)
|
||||
box.set_halign(Gtk.Align.FILL)
|
||||
switch = Gtk.Switch()
|
||||
switch.set_active(app.config.get_per('accounts', self.account, 'active'))
|
||||
switch.set_vexpand(False)
|
||||
switch.set_valign(Gtk.Align.CENTER)
|
||||
switch.set_halign(Gtk.Align.END)
|
||||
if self.account == app.ZEROCONF_ACC_NAME and not app.is_installed('ZEROCONF'):
|
||||
switch.set_sensitive(False)
|
||||
switch.set_active(False)
|
||||
|
||||
switch.connect('notify::active', self._on_enable_switch, self.account)
|
||||
box.pack_start(switch, False, False, 0)
|
||||
if self.account != app.ZEROCONF_ACC_NAME:
|
||||
button = Gtk.Button(label=_('Remove'))
|
||||
button.connect(
|
||||
'clicked', parent.on_remove_account, self.account)
|
||||
button.get_style_context().add_class('destructive-action')
|
||||
button.set_halign(Gtk.Align.END)
|
||||
switch.set_vexpand(False)
|
||||
box.pack_end(button, False, False, 0)
|
||||
self.pack_start(box, True, True, 0)
|
||||
|
||||
def _on_enable_switch(self, switch, param, account):
|
||||
old_state = app.config.get_per('accounts', account, 'active')
|
||||
state = switch.get_active()
|
||||
if old_state == state:
|
||||
return
|
||||
|
||||
if (account in app.connections and
|
||||
app.connections[account].connected > 0):
|
||||
# connecting or connected
|
||||
app.interface.raise_dialog('connected-on-disable-account')
|
||||
switch.set_active(not state)
|
||||
return
|
||||
if state:
|
||||
self.parent.enable_account(account)
|
||||
else:
|
||||
self.parent.disable_account(account)
|
||||
app.config.set_per('accounts', account, 'active', state)
|
||||
|
||||
|
||||
class PreferencesPage(GenericOptionPage):
|
||||
def __init__(self):
|
||||
|
||||
options = [
|
||||
Option(OptionKind.SWITCH, _('Merge Accounts'),
|
||||
OptionType.ACTION, 'merge'),
|
||||
|
||||
Option(OptionKind.SWITCH, _('Use PGP Agent'),
|
||||
OptionType.ACTION, 'agent'),
|
||||
]
|
||||
|
||||
GenericOptionPage.__init__(self, None, None, options)
|
||||
|
||||
|
||||
class AccountPage(GenericOptionPage):
|
||||
def __init__(self, account, parent=None):
|
||||
|
||||
general = partial(
|
||||
self.set_page, GeneralPage(account, self), 'general')
|
||||
connection = partial(
|
||||
self.set_page, ConnectionPage(account, self), 'connection')
|
||||
|
||||
options = [
|
||||
Option(OptionKind.ENTRY, _('Label'),
|
||||
OptionType.ACCOUNT_CONFIG, 'account_label',
|
||||
callback=self._on_account_name_change),
|
||||
|
||||
Option(OptionKind.LOGIN, _('Login'), OptionType.DIALOG,
|
||||
props={'dialog': LoginDialog}),
|
||||
|
||||
Option(OptionKind.ACTION, _('Profile'), OptionType.ACTION,
|
||||
'-profile', props={'action_args': account}),
|
||||
|
||||
Option(OptionKind.CALLBACK, _('General'),
|
||||
name='general', props={'callback': general}),
|
||||
|
||||
Option(OptionKind.CALLBACK, _('Connection'),
|
||||
name='connection', props={'callback': connection}),
|
||||
|
||||
Option(OptionKind.ACTION, _('Import Contacts'), OptionType.ACTION,
|
||||
'-import-contacts', props={'action_args': account}),
|
||||
|
||||
Option(OptionKind.DIALOG, _('Client Certificate'),
|
||||
OptionType.DIALOG, props={'dialog': CertificateDialog}),
|
||||
|
||||
Option(OptionKind.GPG, _('OpenPGP Key'), OptionType.DIALOG,
|
||||
props={'dialog': None}),
|
||||
]
|
||||
|
||||
GenericOptionPage.__init__(self, account, parent, options)
|
||||
self._add_top_buttons(parent)
|
||||
|
||||
def _on_account_name_change(self, account_name, *args):
|
||||
self.parent.update_accounts()
|
||||
|
||||
|
||||
class GeneralPage(GenericOptionPage):
|
||||
def __init__(self, account, parent=None):
|
||||
|
||||
options = [
|
||||
Option(OptionKind.SWITCH, _('Connect on startup'),
|
||||
OptionType.ACCOUNT_CONFIG, 'autoconnect'),
|
||||
|
||||
Option(OptionKind.SWITCH, _('Reconnect when connection is lost'),
|
||||
OptionType.ACCOUNT_CONFIG, 'autoreconnect'),
|
||||
|
||||
Option(OptionKind.SWITCH, _('Save conversations for all contacts'),
|
||||
OptionType.ACCOUNT_CONFIG, 'no_log_for',
|
||||
desc=_('Store conversations on the harddrive')),
|
||||
|
||||
Option(OptionKind.SWITCH, _('Server Message Archive'),
|
||||
OptionType.ACCOUNT_CONFIG, 'sync_logs_with_server',
|
||||
desc=_('Messages get stored on the server.\n'
|
||||
'The archive is used to sync messages\n'
|
||||
'between multiple devices.\n'
|
||||
'XEP-0313')),
|
||||
|
||||
Option(OptionKind.SWITCH, _('Global Status'),
|
||||
OptionType.ACCOUNT_CONFIG, 'sync_with_global_status',
|
||||
desc=_('Synchronise the status of all accounts')),
|
||||
|
||||
Option(OptionKind.SWITCH, _('Message Carbons'),
|
||||
OptionType.ACCOUNT_CONFIG, 'enable_message_carbons',
|
||||
desc=_('All your other online devices get copies\n'
|
||||
'of sent and received messages.\n'
|
||||
'XEP-0280')),
|
||||
|
||||
Option(OptionKind.SWITCH, _('Use file transfer proxies'),
|
||||
OptionType.ACCOUNT_CONFIG, 'use_ft_proxies'),
|
||||
]
|
||||
GenericOptionPage.__init__(self, account, parent, options)
|
||||
|
||||
|
||||
class ConnectionPage(GenericOptionPage):
|
||||
def __init__(self, account, parent=None):
|
||||
|
||||
options = [
|
||||
Option(OptionKind.SWITCH, 'HTTP_PROXY',
|
||||
OptionType.ACCOUNT_CONFIG, 'use_env_http_proxy',
|
||||
desc=_('Use environment variable')),
|
||||
|
||||
Option(OptionKind.PROXY, _('Proxy'),
|
||||
OptionType.ACCOUNT_CONFIG, 'proxy', name='proxy'),
|
||||
|
||||
Option(OptionKind.SWITCH, _('Warn on insecure connection'),
|
||||
OptionType.ACCOUNT_CONFIG,
|
||||
'warn_when_insecure_ssl_connection'),
|
||||
|
||||
Option(OptionKind.SWITCH, _('Send keep-alive packets'),
|
||||
OptionType.ACCOUNT_CONFIG, 'keep_alives_enabled'),
|
||||
|
||||
Option(OptionKind.HOSTNAME, _('Hostname'), OptionType.DIALOG,
|
||||
desc=_('Manually set the hostname for the server'),
|
||||
props={'dialog': CutstomHostnameDialog}),
|
||||
|
||||
Option(OptionKind.ENTRY, _('Resource'),
|
||||
OptionType.ACCOUNT_CONFIG, 'resource'),
|
||||
|
||||
Option(OptionKind.PRIORITY, _('Priority'),
|
||||
OptionType.DIALOG, props={'dialog': PriorityDialog}),
|
||||
]
|
||||
|
||||
GenericOptionPage.__init__(self, account, parent, options)
|
||||
|
||||
|
||||
class ZeroConfPage(GenericOptionPage):
|
||||
def __init__(self, account, parent=None):
|
||||
|
||||
options = [
|
||||
Option(OptionKind.DIALOG, _('Profile'),
|
||||
OptionType.DIALOG, props={'dialog': ZeroconfProfileDialog}),
|
||||
|
||||
Option(OptionKind.SWITCH, _('Connect on startup'),
|
||||
OptionType.ACCOUNT_CONFIG, 'autoconnect',
|
||||
desc=_('Use environment variable')),
|
||||
|
||||
Option(OptionKind.SWITCH, _('Save conversations for all contacts'),
|
||||
OptionType.ACCOUNT_CONFIG, 'no_log_for',
|
||||
desc=_('Store conversations on the harddrive')),
|
||||
|
||||
Option(OptionKind.SWITCH, _('Global Status'),
|
||||
OptionType.ACCOUNT_CONFIG, 'sync_with_global_status',
|
||||
desc=_('Synchronize the status of all accounts')),
|
||||
|
||||
Option(OptionKind.GPG, _('OpenPGP Key'),
|
||||
OptionType.DIALOG, props={'dialog': None}),
|
||||
]
|
||||
|
||||
GenericOptionPage.__init__(self, account, parent, options)
|
||||
self._add_top_buttons(None)
|
||||
|
||||
|
||||
class ZeroconfProfileDialog(OptionsDialog):
|
||||
def __init__(self, account, parent):
|
||||
|
||||
options = [
|
||||
Option(OptionKind.ENTRY, _('First Name'),
|
||||
OptionType.ACCOUNT_CONFIG, 'zeroconf_first_name'),
|
||||
|
||||
Option(OptionKind.ENTRY, _('Last Name'),
|
||||
OptionType.ACCOUNT_CONFIG, 'zeroconf_last_name'),
|
||||
|
||||
Option(OptionKind.ENTRY, _('Jabber ID'),
|
||||
OptionType.ACCOUNT_CONFIG, 'zeroconf_jabber_id'),
|
||||
|
||||
Option(OptionKind.ENTRY, _('Email'),
|
||||
OptionType.ACCOUNT_CONFIG, 'zeroconf_email'),
|
||||
]
|
||||
|
||||
OptionsDialog.__init__(self, parent, _('Profile'),
|
||||
Gtk.DialogFlags.MODAL, options, account)
|
||||
|
||||
|
||||
class PriorityDialog(OptionsDialog):
|
||||
def __init__(self, account, parent):
|
||||
|
||||
neg_priority = app.config.get('enable_negative_priority')
|
||||
if neg_priority:
|
||||
range_ = (-128, 127)
|
||||
else:
|
||||
range_ = (0, 127)
|
||||
|
||||
options = [
|
||||
Option(OptionKind.SWITCH, _('Adjust to status'),
|
||||
OptionType.ACCOUNT_CONFIG, 'adjust_priority_with_status',
|
||||
'adjust'),
|
||||
|
||||
Option(OptionKind.SPIN, _('Priority'),
|
||||
OptionType.ACCOUNT_CONFIG, 'priority',
|
||||
enabledif=('adjust', False), props={'range_': range_}),
|
||||
]
|
||||
|
||||
OptionsDialog.__init__(self, parent, _('Priority'),
|
||||
Gtk.DialogFlags.MODAL, options, account)
|
||||
|
||||
self.connect('destroy', self.on_destroy)
|
||||
|
||||
def on_destroy(self, *args):
|
||||
# Update priority
|
||||
if self.account not in app.connections:
|
||||
return
|
||||
show = app.SHOW_LIST[app.connections[self.account].connected]
|
||||
status = app.connections[self.account].status
|
||||
app.connections[self.account].change_status(show, status)
|
||||
|
||||
|
||||
class CutstomHostnameDialog(OptionsDialog):
|
||||
def __init__(self, account, parent):
|
||||
|
||||
options = [
|
||||
Option(OptionKind.SWITCH, _('Enable'),
|
||||
OptionType.ACCOUNT_CONFIG, 'use_custom_host', name='custom'),
|
||||
|
||||
Option(OptionKind.ENTRY, _('Hostname'),
|
||||
OptionType.ACCOUNT_CONFIG, 'custom_host',
|
||||
enabledif=('custom', True)),
|
||||
|
||||
Option(OptionKind.ENTRY, _('Port'),
|
||||
OptionType.ACCOUNT_CONFIG, 'custom_port',
|
||||
enabledif=('custom', True)),
|
||||
]
|
||||
|
||||
OptionsDialog.__init__(self, parent, _('Connection Options'),
|
||||
Gtk.DialogFlags.MODAL, options, account)
|
||||
|
||||
|
||||
class CertificateDialog(OptionsDialog):
|
||||
def __init__(self, account, parent):
|
||||
|
||||
options = [
|
||||
Option(OptionKind.FILECHOOSER, _('Client Certificate'),
|
||||
OptionType.ACCOUNT_CONFIG, 'client_cert',
|
||||
props={'filefilter': (_('PKCS12 Files'), '*.p12')}),
|
||||
|
||||
Option(OptionKind.SWITCH, _('Encrypted Certificate'),
|
||||
OptionType.ACCOUNT_CONFIG, 'client_cert_encrypted'),
|
||||
]
|
||||
|
||||
OptionsDialog.__init__(self, parent, _('Certificate Options'),
|
||||
Gtk.DialogFlags.MODAL, options, account)
|
||||
|
||||
|
||||
class LoginDialog(OptionsDialog):
|
||||
def __init__(self, account, parent):
|
||||
|
||||
options = [
|
||||
Option(OptionKind.ENTRY, _('Password'),
|
||||
OptionType.ACCOUNT_CONFIG, 'password', name='password',
|
||||
enabledif=('savepass', True)),
|
||||
|
||||
Option(OptionKind.SWITCH, _('Save Password'),
|
||||
OptionType.ACCOUNT_CONFIG, 'savepass', name='savepass'),
|
||||
|
||||
Option(OptionKind.CHANGEPASSWORD, _('Change Password'),
|
||||
OptionType.DIALOG, callback=self.on_password_change,
|
||||
props={'dialog': None}),
|
||||
]
|
||||
|
||||
OptionsDialog.__init__(self, parent, _('Login Options'),
|
||||
Gtk.DialogFlags.MODAL, options, account)
|
||||
|
||||
self.connect('destroy', self.on_destroy)
|
||||
|
||||
def on_password_change(self, new_password, data):
|
||||
passwords.save_password(self.account, new_password)
|
||||
|
||||
def on_destroy(self, *args):
|
||||
savepass = app.config.get_per('accounts', self.account, 'savepass')
|
||||
if not savepass:
|
||||
passwords.save_password(self.account, '')
|
||||
|
|
@ -1,558 +0,0 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
## src/adhoc_commands.py
|
||||
##
|
||||
## Copyright (C) 2006 Nikos Kouremenos <kourem AT gmail.com>
|
||||
## Copyright (C) 2006-2007 Tomasz Melcer <liori AT exroot.org>
|
||||
## Copyright (C) 2006-2014 Yann Leboulanger <asterix AT lagaule.org>
|
||||
## Copyright (C) 2008 Jonathan Schleifer <js-gajim AT webkeks.org>
|
||||
## Stephan Erb <steve-e AT h3c.de>
|
||||
##
|
||||
## This file is part of Gajim.
|
||||
##
|
||||
## Gajim is free software; you can redistribute it and/or modify
|
||||
## it under the terms of the GNU General Public License as published
|
||||
## by the Free Software Foundation; version 3 only.
|
||||
##
|
||||
## Gajim is distributed in the hope that it will be useful,
|
||||
## but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
## GNU General Public License for more details.
|
||||
##
|
||||
## You should have received a copy of the GNU General Public License
|
||||
## along with Gajim. If not, see <http://www.gnu.org/licenses/>.
|
||||
##
|
||||
|
||||
# FIXME: think if we need caching command list. it may be wrong if there will
|
||||
# be entities that often change the list, it may be slow to fetch it every time
|
||||
|
||||
from gi.repository import Gtk
|
||||
|
||||
import nbxmpp
|
||||
from gajim.common import app
|
||||
from gajim.common.modules import dataforms
|
||||
from gajim.common import ged
|
||||
|
||||
from gajim import gtkgui_helpers
|
||||
from gajim.gtk import HigDialog
|
||||
from gajim import dataforms_widget
|
||||
|
||||
|
||||
class CommandWindow:
|
||||
"""
|
||||
Class for a window for single ad-hoc commands session
|
||||
|
||||
Note, that there might be more than one for one account/jid pair in one
|
||||
moment.
|
||||
|
||||
TODO: Maybe put this window into MessageWindow? consider this when it will
|
||||
be possible to manage more than one window of one.
|
||||
TODO: Account/jid pair in MessageWindowMgr.
|
||||
TODO: GTK 2.10 has a special wizard-widget, consider using it...
|
||||
"""
|
||||
|
||||
def __init__(self, account, jid, commandnode=None):
|
||||
"""
|
||||
Create new window
|
||||
"""
|
||||
|
||||
# an account object
|
||||
self._con = app.connections[account]
|
||||
self.jid = jid
|
||||
self.commandnode = commandnode
|
||||
self.data_form_widget = None
|
||||
self.stage_finish_cb = None
|
||||
self.stage_back_button_cb = None
|
||||
self.stage_forward_button_cb = None
|
||||
self.stage_execute_button_cb = None
|
||||
self.stage_close_button_cb = None
|
||||
self.stage_restart_button_cb = None
|
||||
self.stage_window_delete_cb = None
|
||||
|
||||
# retrieving widgets from xml
|
||||
self.xml = gtkgui_helpers.get_gtk_builder('adhoc_commands_window.ui')
|
||||
self.window = self.xml.get_object('adhoc_commands_window')
|
||||
self.window.connect('delete-event',
|
||||
self.on_adhoc_commands_window_delete_event)
|
||||
for name in ('restart_button', 'back_button', 'forward_button',
|
||||
'execute_button', 'finish_button', 'close_button', 'stages_notebook',
|
||||
'retrieving_commands_stage_vbox', 'retrieving_commands_spinner',
|
||||
'command_list_stage_vbox', 'command_treeview',
|
||||
'sending_form_stage_vbox', 'sending_form_spinner',
|
||||
'notes_label', 'no_commands_stage_vbox',
|
||||
'error_stage_vbox', 'error_description_label'):
|
||||
setattr(self, name, self.xml.get_object(name))
|
||||
|
||||
self.command_store = Gtk.ListStore(str, str)
|
||||
self.command_treeview.set_model(self.command_store)
|
||||
renderer = Gtk.CellRendererText()
|
||||
column = Gtk.TreeViewColumn("Command", renderer, text=0)
|
||||
self.command_treeview.append_column(column)
|
||||
|
||||
app.ged.register_event_handler(
|
||||
'adhoc-command-error', ged.CORE, self._on_command_error)
|
||||
app.ged.register_event_handler(
|
||||
'adhoc-command-list', ged.CORE, self._on_command_list)
|
||||
app.ged.register_event_handler('adhoc-command-action-response',
|
||||
ged.CORE,
|
||||
self._on_action_response)
|
||||
|
||||
self.initiate()
|
||||
|
||||
def initiate(self):
|
||||
|
||||
self.commandlist = None # a list of (commandname, commanddescription)
|
||||
|
||||
# command's data
|
||||
self.sessionid = None
|
||||
self.dataform = None
|
||||
self.allow_stage3_close = False
|
||||
|
||||
# creating data forms widget
|
||||
if self.data_form_widget:
|
||||
self.sending_form_stage_vbox.remove(self.data_form_widget)
|
||||
self.data_form_widget.destroy()
|
||||
self.data_form_widget = dataforms_widget.DataFormWidget()
|
||||
self.data_form_widget.show()
|
||||
self.sending_form_stage_vbox.pack_start(self.data_form_widget, True, True, 0)
|
||||
|
||||
if self.commandnode:
|
||||
# Execute command
|
||||
self.stage3()
|
||||
else:
|
||||
# setting initial stage
|
||||
self.stage1()
|
||||
|
||||
# displaying the window
|
||||
self.window.set_title(_('Ad-hoc Commands - Gajim'))
|
||||
self.xml.connect_signals(self)
|
||||
self.window.show_all()
|
||||
|
||||
self.restart_button.set_sensitive(False)
|
||||
|
||||
# These functions are set up by appropriate stageX methods.
|
||||
def stage_finish(self, *anything):
|
||||
if self.stage_finish_cb:
|
||||
self.stage_finish_cb(*anything)
|
||||
|
||||
# Widget callbacks...
|
||||
def on_back_button_clicked(self, *anything):
|
||||
if self.stage_back_button_cb:
|
||||
return self.stage_back_button_cb(*anything)
|
||||
return False
|
||||
|
||||
def on_forward_button_clicked(self, *anything):
|
||||
if self.stage_forward_button_cb:
|
||||
return self.stage_forward_button_cb(*anything)
|
||||
return False
|
||||
|
||||
def on_execute_button_clicked(self, *anything):
|
||||
if self.stage_execute_button_cb:
|
||||
return self.stage_execute_button_cb(*anything)
|
||||
return False
|
||||
|
||||
def on_finish_button_clicked(self, *anything):
|
||||
if self.stage_finish_button_cb:
|
||||
return self.stage_finish_button_cb(*anything)
|
||||
return False
|
||||
|
||||
def on_close_button_clicked(self, *anything):
|
||||
if self.stage_close_button_cb:
|
||||
return self.stage_close_button_cb(*anything)
|
||||
return False
|
||||
|
||||
def on_restart_button_clicked(self, *anything):
|
||||
if self.stage_restart_button_cb:
|
||||
return self.stage_restart_button_cb(*anything)
|
||||
return False
|
||||
|
||||
def on_adhoc_commands_window_destroy(self, *anything):
|
||||
app.ged.remove_event_handler(
|
||||
'adhoc-command-error', ged.CORE, self._on_command_error)
|
||||
app.ged.remove_event_handler(
|
||||
'adhoc-command-list', ged.CORE, self._on_command_list)
|
||||
app.ged.remove_event_handler('adhoc-command-action-response',
|
||||
ged.CORE,
|
||||
self._on_action_response)
|
||||
|
||||
def on_adhoc_commands_window_delete_event(self, *anything):
|
||||
if self.stage_window_delete_cb:
|
||||
return self.stage_window_delete_cb(self.window)
|
||||
return False
|
||||
|
||||
def __del__(self):
|
||||
print('Object has been deleted.')
|
||||
|
||||
# stage 1: waiting for command list
|
||||
def stage1(self):
|
||||
"""
|
||||
Prepare the first stage. Request command list, set appropriate state of
|
||||
widgets
|
||||
"""
|
||||
# close old stage...
|
||||
self.stage_finish()
|
||||
|
||||
# show the stage
|
||||
self.stages_notebook.set_current_page(
|
||||
self.stages_notebook.page_num(
|
||||
self.retrieving_commands_stage_vbox))
|
||||
|
||||
# set widgets' state
|
||||
self.close_button.set_sensitive(True)
|
||||
self.back_button.set_sensitive(False)
|
||||
self.forward_button.set_sensitive(False)
|
||||
self.execute_button.set_sensitive(False)
|
||||
self.finish_button.set_sensitive(False)
|
||||
|
||||
# request command list
|
||||
self._con.get_module('AdHocCommands').request_command_list(self.jid)
|
||||
self.retrieving_commands_spinner.start()
|
||||
|
||||
# setup the callbacks
|
||||
self.stage_finish_cb = self.stage1_finish
|
||||
self.stage_close_button_cb = self.stage1_close_button_clicked
|
||||
self.stage_restart_button_cb = self.stage1_restart_button_clicked
|
||||
self.stage_window_delete_cb = \
|
||||
self.stage1_adhoc_commands_window_delete_event
|
||||
|
||||
def stage1_finish(self):
|
||||
self.retrieving_commands_spinner.stop()
|
||||
|
||||
def stage1_close_button_clicked(self, widget):
|
||||
# cancelling in this stage is not critical, so we don't
|
||||
# show any popups to user
|
||||
self.stage_finish()
|
||||
self.window.destroy()
|
||||
|
||||
def stage1_restart_button_clicked(self, widget):
|
||||
self.stage_finish()
|
||||
self.restart()
|
||||
|
||||
def stage1_adhoc_commands_window_delete_event(self, widget):
|
||||
self.stage1_finish()
|
||||
return True
|
||||
|
||||
# stage 2: choosing the command to execute
|
||||
def stage2(self):
|
||||
"""
|
||||
Populate the command list
|
||||
"""
|
||||
# close old stage
|
||||
self.stage_finish()
|
||||
|
||||
assert len(self.commandlist)>0
|
||||
|
||||
self.stages_notebook.set_current_page(
|
||||
self.stages_notebook.page_num(self.command_list_stage_vbox))
|
||||
|
||||
self.close_button.set_sensitive(True)
|
||||
self.back_button.set_sensitive(False)
|
||||
self.forward_button.set_sensitive(True)
|
||||
self.execute_button.set_sensitive(False)
|
||||
self.finish_button.set_sensitive(False)
|
||||
|
||||
# build the commands list
|
||||
self.command_store.clear()
|
||||
for (commandnode, commandname) in self.commandlist:
|
||||
self.command_store.append([commandname, commandnode])
|
||||
self.command_treeview.get_selection().select_iter(self.command_store.get_iter_first())
|
||||
|
||||
self.stage_finish_cb = self.stage2_finish
|
||||
self.stage_close_button_cb = self.stage2_close_button_clicked
|
||||
self.stage_restart_button_cb = self.stage2_restart_button_clicked
|
||||
self.stage_forward_button_cb = self.stage2_forward_button_clicked
|
||||
self.stage_window_delete_cb = None
|
||||
|
||||
def stage2_finish(self):
|
||||
"""
|
||||
Save selected command to self.commandnode
|
||||
"""
|
||||
model, treeiter = self.command_treeview.get_selection().get_selected()
|
||||
self.commandnode = model[treeiter][1]
|
||||
|
||||
def stage2_close_button_clicked(self, widget):
|
||||
self.stage_finish()
|
||||
self.window.destroy()
|
||||
|
||||
def stage2_restart_button_clicked(self, widget):
|
||||
self.stage_finish()
|
||||
self.restart()
|
||||
|
||||
def stage2_forward_button_clicked(self, widget):
|
||||
self.stage3()
|
||||
|
||||
def on_check_commands_1_button_clicked(self, widget):
|
||||
self.stage1()
|
||||
|
||||
# stage 3: command invocation
|
||||
def stage3(self):
|
||||
# close old stage
|
||||
self.stage_finish()
|
||||
|
||||
self.form_status = None
|
||||
|
||||
self.stages_notebook.set_current_page(
|
||||
self.stages_notebook.page_num(
|
||||
self.sending_form_stage_vbox))
|
||||
|
||||
self.restart_button.set_sensitive(True)
|
||||
self.close_button.set_sensitive(True)
|
||||
self.back_button.set_sensitive(False)
|
||||
self.forward_button.set_sensitive(False)
|
||||
self.execute_button.set_sensitive(False)
|
||||
self.finish_button.set_sensitive(False)
|
||||
|
||||
self.stage3_submit_form()
|
||||
|
||||
self.stage_finish_cb = None
|
||||
self.stage_back_button_cb = self.stage3_back_button_clicked
|
||||
self.stage_forward_button_cb = self.stage3_forward_button_clicked
|
||||
self.stage_execute_button_cb = self.stage3_execute_button_clicked
|
||||
self.stage_finish_button_cb = self.stage3_finish_button_clicked
|
||||
self.stage_close_button_cb = self.stage3_close_button_clicked
|
||||
self.stage_restart_button_cb = self.stage3_restart_button_clicked
|
||||
self.stage_window_delete_cb = self.stage3_close_button_clicked
|
||||
|
||||
def stage3_can_close(self, cb):
|
||||
if self.form_status == 'completed':
|
||||
cb()
|
||||
return
|
||||
|
||||
def on_yes(button):
|
||||
self._con.get_module('AdHocCommands').send_cancel(
|
||||
self.jid, self.commandnode, self.sessionid)
|
||||
dialog.destroy()
|
||||
cb()
|
||||
|
||||
dialog = HigDialog(self.window, Gtk.MessageType.WARNING,
|
||||
Gtk.ButtonsType.YES_NO, _('Cancel confirmation'),
|
||||
_('You are in process of executing command. Do you really want to '
|
||||
'cancel it?'), on_response_yes=on_yes)
|
||||
dialog.popup()
|
||||
|
||||
def stage3_close_button_clicked(self, widget):
|
||||
"""
|
||||
We are in the middle of executing command. Ask user if he really want to
|
||||
cancel the process, then cancel it
|
||||
"""
|
||||
# this works also as a handler for window_delete_event, so we have to
|
||||
# return appropriate values
|
||||
if self.allow_stage3_close:
|
||||
return False
|
||||
|
||||
def on_ok():
|
||||
self.allow_stage3_close = True
|
||||
self.window.destroy()
|
||||
|
||||
self.stage3_can_close(on_ok)
|
||||
|
||||
return True # Block event, don't close window
|
||||
|
||||
def stage3_restart_button_clicked(self, widget):
|
||||
def on_ok():
|
||||
self.restart()
|
||||
|
||||
self.stage3_can_close(on_ok)
|
||||
|
||||
def stage3_back_button_clicked(self, widget):
|
||||
self.stage3_submit_form('prev')
|
||||
|
||||
def stage3_forward_button_clicked(self, widget):
|
||||
self.stage3_submit_form('next')
|
||||
|
||||
def stage3_execute_button_clicked(self, widget):
|
||||
self.stage3_submit_form('execute')
|
||||
|
||||
def stage3_finish_button_clicked(self, widget):
|
||||
self.stage3_submit_form('complete')
|
||||
|
||||
def stage3_submit_form(self, action='execute'):
|
||||
self.data_form_widget.set_sensitive(False)
|
||||
|
||||
if self.data_form_widget.get_data_form():
|
||||
df = self.data_form_widget.get_data_form()
|
||||
if not df.is_valid():
|
||||
app.interface.raise_dialog(
|
||||
'invalid-form', transient_for=self.window)
|
||||
self.data_form_widget.set_sensitive(True)
|
||||
return
|
||||
self.data_form_widget.data_form.type_ = 'submit'
|
||||
else:
|
||||
self.data_form_widget.hide()
|
||||
|
||||
self.close_button.set_sensitive(True)
|
||||
self.back_button.set_sensitive(False)
|
||||
self.forward_button.set_sensitive(False)
|
||||
self.execute_button.set_sensitive(False)
|
||||
self.finish_button.set_sensitive(False)
|
||||
|
||||
self.sending_form_spinner.start()
|
||||
self._con.get_module('AdHocCommands').send_command(
|
||||
self.jid, self.commandnode, self.sessionid,
|
||||
self.data_form_widget.data_form, action)
|
||||
|
||||
def stage3_next_form(self, command):
|
||||
if not isinstance(command, nbxmpp.Node):
|
||||
self.stage5(error=_('Service sent malformed data'), senderror=True)
|
||||
return
|
||||
|
||||
self.sending_form_spinner.stop()
|
||||
|
||||
if not self.sessionid:
|
||||
self.sessionid = command.getAttr('sessionid')
|
||||
elif self.sessionid != command.getAttr('sessionid'):
|
||||
self.stage5(error=_('Service changed the session identifier.'),
|
||||
senderror=True)
|
||||
return
|
||||
|
||||
self.form_status = command.getAttr('status')
|
||||
|
||||
self.commandnode = command.getAttr('node')
|
||||
if command.getTag('x'):
|
||||
self.dataform = dataforms.ExtendForm(node=command.getTag('x'))
|
||||
|
||||
self.data_form_widget.set_sensitive(True)
|
||||
try:
|
||||
self.data_form_widget.selectable = True
|
||||
self.data_form_widget.data_form = self.dataform
|
||||
except dataforms.Error:
|
||||
self.stage5(error=_('Service sent malformed data'),
|
||||
senderror=True)
|
||||
return
|
||||
self.data_form_widget.show()
|
||||
if self.data_form_widget.title:
|
||||
self.window.set_title(_('%s - Ad-hoc Commands - Gajim') % \
|
||||
self.data_form_widget.title)
|
||||
else:
|
||||
self.data_form_widget.hide()
|
||||
|
||||
actions = command.getTag('actions')
|
||||
if actions:
|
||||
# actions, actions, actions...
|
||||
self.close_button.set_sensitive(True)
|
||||
self.back_button.set_sensitive(actions.getTag('prev') is not None)
|
||||
self.forward_button.set_sensitive(
|
||||
actions.getTag('next') is not None)
|
||||
self.execute_button.set_sensitive(True)
|
||||
self.finish_button.set_sensitive(actions.getTag('complete') is not \
|
||||
None)
|
||||
else:
|
||||
self.close_button.set_sensitive(True)
|
||||
self.back_button.set_sensitive(False)
|
||||
self.forward_button.set_sensitive(False)
|
||||
self.execute_button.set_sensitive(True)
|
||||
self.finish_button.set_sensitive(False)
|
||||
|
||||
if self.form_status == 'completed':
|
||||
self.close_button.set_sensitive(True)
|
||||
self.back_button.hide()
|
||||
self.forward_button.hide()
|
||||
self.execute_button.hide()
|
||||
self.finish_button.hide()
|
||||
self.close_button.show()
|
||||
self.stage_window_delete_cb = self.stage3_close_button_clicked
|
||||
|
||||
note = command.getTag('note')
|
||||
if note:
|
||||
self.notes_label.set_text(note.getData())
|
||||
self.notes_label.set_no_show_all(False)
|
||||
self.notes_label.show()
|
||||
else:
|
||||
self.notes_label.set_no_show_all(True)
|
||||
self.notes_label.hide()
|
||||
|
||||
def restart(self):
|
||||
self.commandnode = None
|
||||
self.initiate()
|
||||
|
||||
# stage 4: no commands are exposed
|
||||
def stage4(self):
|
||||
"""
|
||||
Display the message. Wait for user to close the window
|
||||
"""
|
||||
# close old stage
|
||||
self.stage_finish()
|
||||
|
||||
self.stages_notebook.set_current_page(
|
||||
self.stages_notebook.page_num(self.no_commands_stage_vbox))
|
||||
|
||||
self.close_button.set_sensitive(True)
|
||||
self.back_button.set_sensitive(False)
|
||||
self.forward_button.set_sensitive(False)
|
||||
self.execute_button.set_sensitive(False)
|
||||
self.finish_button.set_sensitive(False)
|
||||
|
||||
self.stage_finish_cb = None
|
||||
self.stage_close_button_cb = self.stage4_close_button_clicked
|
||||
self.stage_restart_button_cb = self.stage4_restart_button_clicked
|
||||
self.stage_window_delete_cb = None
|
||||
|
||||
def stage4_close_button_clicked(self, widget):
|
||||
self.window.destroy()
|
||||
|
||||
def stage4_restart_button_clicked(self, widget):
|
||||
self.restart()
|
||||
|
||||
def on_check_commands_2_button_clicked(self, widget):
|
||||
self.stage1()
|
||||
|
||||
# stage 5: an error has occurred
|
||||
def stage5(self, error=None, errorid=None, senderror=False):
|
||||
"""
|
||||
Display the error message. Wait for user to close the window
|
||||
"""
|
||||
# FIXME: sending error to responder
|
||||
# close old stage
|
||||
self.stage_finish()
|
||||
|
||||
assert errorid or error
|
||||
|
||||
if errorid:
|
||||
# we've got error code, display appropriate message
|
||||
try:
|
||||
errorname = nbxmpp.NS_STANZAS + ' ' + str(errorid)
|
||||
errordesc = nbxmpp.ERRORS[errorname][2]
|
||||
error = errordesc
|
||||
del errorname, errordesc
|
||||
except KeyError: # when stanza doesn't have error description
|
||||
error = _('Service returned an error.')
|
||||
elif error:
|
||||
# we've got error message
|
||||
pass
|
||||
else:
|
||||
# we don't know what's that, bailing out
|
||||
assert False
|
||||
|
||||
self.stages_notebook.set_current_page(
|
||||
self.stages_notebook.page_num(self.error_stage_vbox))
|
||||
|
||||
self.close_button.set_sensitive(True)
|
||||
self.back_button.hide()
|
||||
self.forward_button.hide()
|
||||
self.execute_button.hide()
|
||||
self.finish_button.hide()
|
||||
|
||||
self.error_description_label.set_text(error)
|
||||
|
||||
self.stage_finish_cb = None
|
||||
self.stage_close_button_cb = self.stage5_close_button_clicked
|
||||
self.stage_restart_button_cb = self.stage5_restart_button_clicked
|
||||
self.stage_window_delete_cb = None
|
||||
|
||||
def stage5_close_button_clicked(self, widget):
|
||||
self.window.destroy()
|
||||
|
||||
def stage5_restart_button_clicked(self, widget):
|
||||
self.restart()
|
||||
|
||||
def _on_command_error(self, obj):
|
||||
self.stage5(errorid=obj.error)
|
||||
|
||||
def _on_command_list(self, obj):
|
||||
self.commandlist = obj.commandlist
|
||||
if not self.commandlist:
|
||||
self.stage4()
|
||||
else:
|
||||
self.stage2()
|
||||
|
||||
def _on_action_response(self, obj):
|
||||
self.stage3_next_form(obj.command)
|
||||
|
|
@ -1,4 +1,3 @@
|
|||
#
|
||||
# Copyright (C) 2017 Philipp Hörist <philipp AT hoerist.com>
|
||||
#
|
||||
# This file is part of Gajim.
|
||||
|
|
@ -15,31 +14,37 @@
|
|||
# You should have received a copy of the GNU General Public License
|
||||
# along with Gajim. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
from gi.repository import Gtk
|
||||
from gi.repository import Gdk
|
||||
|
||||
from gajim.common import app
|
||||
from gajim.common import helpers
|
||||
from gajim.common.app import interface
|
||||
from gajim.common.exceptions import GajimGeneralException
|
||||
from gajim import config
|
||||
from gajim import dialogs
|
||||
from gajim.gtk import shortcuts_window
|
||||
from gajim.accounts_window import AccountsWindow
|
||||
|
||||
import gajim.plugins.gui
|
||||
from gajim import disco
|
||||
|
||||
from gajim.gtk.dialogs import ShortcutsWindow
|
||||
from gajim.gtk.history_sync import HistorySyncAssistant
|
||||
from gajim.gtk.server_info import ServerInfoDialog
|
||||
from gajim.gtk.mam_preferences import MamPreferences
|
||||
from gajim.gtk.preferences import Preferences
|
||||
from gajim.gtk import JoinGroupchatWindow
|
||||
from gajim.gtk import StartChatDialog
|
||||
from gajim.gtk import AddNewContactWindow
|
||||
from gajim.gtk import SingleMessageWindow
|
||||
from gajim.gtk import XMLConsoleWindow
|
||||
from gajim.gtk import AboutDialog
|
||||
from gajim.gtk import PrivacyListsWindow
|
||||
from gajim.gtk import ManageBookmarksWindow
|
||||
from gajim.gtk import FeaturesDialog
|
||||
from gajim.gtk import AccountCreationWizard
|
||||
from gajim.gtk import HistoryWindow
|
||||
from gajim.gtk.join_groupchat import JoinGroupchatWindow
|
||||
from gajim.gtk.start_chat import StartChatDialog
|
||||
from gajim.gtk.add_contact import AddNewContactWindow
|
||||
from gajim.gtk.single_message import SingleMessageWindow
|
||||
from gajim.gtk.xml_console import XMLConsoleWindow
|
||||
from gajim.gtk.about import AboutDialog
|
||||
from gajim.gtk.privacy_list import PrivacyListsWindow
|
||||
from gajim.gtk.bookmarks import ManageBookmarksWindow
|
||||
from gajim.gtk.features import FeaturesDialog
|
||||
from gajim.gtk.account_wizard import AccountCreationWizard
|
||||
from gajim.gtk.history import HistoryWindow
|
||||
from gajim.gtk.accounts import AccountsWindow
|
||||
from gajim.gtk.proxies import ManageProxies
|
||||
from gajim.gtk.discovery import ServiceDiscoveryWindow
|
||||
from gajim.gtk.blocking import BlockingList
|
||||
|
||||
|
||||
# General Actions
|
||||
|
|
@ -126,29 +131,34 @@ def on_service_disco(action, param):
|
|||
else:
|
||||
try:
|
||||
# Object will add itself to the window dict
|
||||
disco.ServiceDiscoveryWindow(account, address_entry=True)
|
||||
ServiceDiscoveryWindow(account, address_entry=True)
|
||||
except GajimGeneralException:
|
||||
pass
|
||||
|
||||
|
||||
def on_join_gc(action, param):
|
||||
account = None
|
||||
def on_join_gc(_action, param):
|
||||
account, jid = None, None
|
||||
if param is None:
|
||||
if not app.get_connected_accounts():
|
||||
return
|
||||
else:
|
||||
account = param.get_string()
|
||||
account, jid = param.get_strv()
|
||||
if not jid:
|
||||
jid = None
|
||||
window = app.get_app_window(JoinGroupchatWindow)
|
||||
if window is None:
|
||||
JoinGroupchatWindow(account, None)
|
||||
JoinGroupchatWindow(account, jid)
|
||||
else:
|
||||
window.present()
|
||||
|
||||
|
||||
def on_add_contact(action, param):
|
||||
window = app.get_app_window(AddNewContactWindow)
|
||||
def on_add_contact(_action, param):
|
||||
account, jid = param.get_strv()
|
||||
if not jid:
|
||||
jid = None
|
||||
window = app.get_app_window(AddNewContactWindow, account)
|
||||
if window is None:
|
||||
AddNewContactWindow(param.get_string())
|
||||
AddNewContactWindow(account, jid)
|
||||
else:
|
||||
window.present()
|
||||
|
||||
|
|
@ -169,11 +179,6 @@ def on_merge_accounts(action, param):
|
|||
app.interface.roster.setup_and_draw_roster()
|
||||
|
||||
|
||||
def on_use_pgp_agent(action, param):
|
||||
action.set_state(param)
|
||||
app.config.set('use_gpg_agent', param.get_boolean())
|
||||
|
||||
|
||||
def on_add_account(action, param):
|
||||
if 'account_creation_wizard' in app.interface.instances:
|
||||
app.interface.instances['account_creation_wizard'].window.present()
|
||||
|
|
@ -202,6 +207,15 @@ def on_mam_preferences(action, param):
|
|||
window.present()
|
||||
|
||||
|
||||
def on_blocking_list(action, param):
|
||||
account = param.get_string()
|
||||
window = app.get_app_window(MamPreferences, account)
|
||||
if window is None:
|
||||
BlockingList(account)
|
||||
else:
|
||||
window.present()
|
||||
|
||||
|
||||
def on_history_sync(action, param):
|
||||
account = param.get_string()
|
||||
if 'history_sync' in interface.instances[account]:
|
||||
|
|
@ -242,8 +256,7 @@ def on_manage_proxies(action, param):
|
|||
if 'manage_proxies' in app.interface.instances:
|
||||
app.interface.instances['manage_proxies'].window.present()
|
||||
else:
|
||||
app.interface.instances['manage_proxies'] = \
|
||||
config.ManageProxiesWindow(interface.roster.window)
|
||||
app.interface.instances['manage_proxies'] = ManageProxies()
|
||||
|
||||
# Admin Actions
|
||||
|
||||
|
|
@ -264,25 +277,21 @@ def on_update_motd(action, param):
|
|||
|
||||
def on_delete_motd(action, param):
|
||||
account = param.get_string()
|
||||
server = app.config.get_per('accounts', account, 'hostname')
|
||||
server += '/announce/motd/delete'
|
||||
app.connections[account].send_motd(server)
|
||||
app.connections[account].get_module('Announce').delete_motd()
|
||||
|
||||
# Help Actions
|
||||
|
||||
|
||||
def on_contents(action, param):
|
||||
helpers.launch_browser_mailer(
|
||||
'url', 'https://dev.gajim.org/gajim/gajim/wikis')
|
||||
helpers.open_uri('https://dev.gajim.org/gajim/gajim/wikis')
|
||||
|
||||
|
||||
def on_faq(action, param):
|
||||
helpers.launch_browser_mailer(
|
||||
'url', 'https://dev.gajim.org/gajim/gajim/wikis/help/gajimfaq')
|
||||
helpers.open_uri('https://dev.gajim.org/gajim/gajim/wikis/help/gajimfaq')
|
||||
|
||||
|
||||
def on_keyboard_shortcuts(action, param):
|
||||
shortcuts_window.show(app.app.get_active_window())
|
||||
ShortcutsWindow()
|
||||
|
||||
|
||||
def on_features(action, param):
|
||||
|
|
@ -304,10 +313,7 @@ def on_file_transfers(action, param):
|
|||
|
||||
|
||||
def on_history(action, param):
|
||||
if 'logs' in interface.instances:
|
||||
interface.instances['logs'].window.present()
|
||||
else:
|
||||
interface.instances['logs'] = HistoryWindow()
|
||||
on_browse_history(action, param)
|
||||
|
||||
|
||||
def on_open_event(action, param):
|
||||
|
|
@ -338,3 +344,41 @@ def show_next_pending_event(action, param):
|
|||
if not event:
|
||||
return
|
||||
app.interface.handle_event(account, jid, event.type_)
|
||||
|
||||
|
||||
def open_link(_action, param):
|
||||
uri = param.get_string()
|
||||
helpers.open_uri(uri)
|
||||
|
||||
|
||||
def copy_text(_action, param):
|
||||
text = param.get_string()
|
||||
clip = Gtk.Clipboard.get(Gdk.SELECTION_CLIPBOARD)
|
||||
clip.set_text(text, -1)
|
||||
|
||||
|
||||
def start_chat(_action, param):
|
||||
account, jid = param.get_strv()
|
||||
app.interface.new_chat_from_jid(account, jid)
|
||||
|
||||
|
||||
def join_groupchat(_action, param):
|
||||
account, jid = param.get_strv()
|
||||
room_jid = jid.split('?')[0]
|
||||
app.interface.join_gc_minimal(account, room_jid)
|
||||
|
||||
|
||||
def on_browse_history(_action, param):
|
||||
jid, account = None, None
|
||||
if param is not None:
|
||||
dict_ = param.unpack()
|
||||
jid = dict_.get('jid')
|
||||
account = dict_.get('account')
|
||||
|
||||
window = app.get_app_window(HistoryWindow)
|
||||
if window is None:
|
||||
HistoryWindow(jid, account)
|
||||
else:
|
||||
window.present()
|
||||
if jid is not None and account is not None:
|
||||
window.open_history(jid, account)
|
||||
|
|
|
|||
|
|
@ -1,102 +1,163 @@
|
|||
# -*- coding:utf-8 -*-
|
||||
## src/gajim.py
|
||||
##
|
||||
## Copyright (C) 2003-2017 Yann Leboulanger <asterix AT lagaule.org>
|
||||
## Copyright (C) 2004-2005 Vincent Hanquez <tab AT snarc.org>
|
||||
## Copyright (C) 2005 Alex Podaras <bigpod AT gmail.com>
|
||||
## Norman Rasmussen <norman AT rasmussen.co.za>
|
||||
## Stéphan Kochen <stephan AT kochen.nl>
|
||||
## Copyright (C) 2005-2006 Dimitur Kirov <dkirov AT gmail.com>
|
||||
## Alex Mauer <hawke AT hawkesnest.net>
|
||||
## Copyright (C) 2005-2007 Travis Shirk <travis AT pobox.com>
|
||||
## Nikos Kouremenos <kourem AT gmail.com>
|
||||
## Copyright (C) 2006 Junglecow J <junglecow AT gmail.com>
|
||||
## Stefan Bethge <stefan AT lanpartei.de>
|
||||
## Copyright (C) 2006-2008 Jean-Marie Traissard <jim AT lapin.org>
|
||||
## Copyright (C) 2007 Lukas Petrovicky <lukas AT petrovicky.net>
|
||||
## James Newton <redshodan AT gmail.com>
|
||||
## Copyright (C) 2007-2008 Brendan Taylor <whateley AT gmail.com>
|
||||
## Julien Pivotto <roidelapluie AT gmail.com>
|
||||
## Stephan Erb <steve-e AT h3c.de>
|
||||
## Copyright (C) 2008 Jonathan Schleifer <js-gajim AT webkeks.org>
|
||||
## Copyright (C) 2016-2017 Emmanuel Gil Peyrot <linkmauve AT linkmauve.fr>
|
||||
## Philipp Hörist <philipp AT hoerist.com>
|
||||
##
|
||||
## This file is part of Gajim.
|
||||
##
|
||||
## Gajim is free software; you can redistribute it and/or modify
|
||||
## it under the terms of the GNU General Public License as published
|
||||
## by the Free Software Foundation; version 3 only.
|
||||
##
|
||||
## Gajim is distributed in the hope that it will be useful,
|
||||
## but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
## GNU General Public License for more details.
|
||||
##
|
||||
## You should have received a copy of the GNU General Public License
|
||||
## along with Gajim. If not, see <http://www.gnu.org/licenses/>.
|
||||
##
|
||||
# Copyright (C) 2003-2017 Yann Leboulanger <asterix AT lagaule.org>
|
||||
# Copyright (C) 2004-2005 Vincent Hanquez <tab AT snarc.org>
|
||||
# Copyright (C) 2005 Alex Podaras <bigpod AT gmail.com>
|
||||
# Norman Rasmussen <norman AT rasmussen.co.za>
|
||||
# Stéphan Kochen <stephan AT kochen.nl>
|
||||
# Copyright (C) 2005-2006 Dimitur Kirov <dkirov AT gmail.com>
|
||||
# Alex Mauer <hawke AT hawkesnest.net>
|
||||
# Copyright (C) 2005-2007 Travis Shirk <travis AT pobox.com>
|
||||
# Nikos Kouremenos <kourem AT gmail.com>
|
||||
# Copyright (C) 2006 Junglecow J <junglecow AT gmail.com>
|
||||
# Stefan Bethge <stefan AT lanpartei.de>
|
||||
# Copyright (C) 2006-2008 Jean-Marie Traissard <jim AT lapin.org>
|
||||
# Copyright (C) 2007 Lukas Petrovicky <lukas AT petrovicky.net>
|
||||
# James Newton <redshodan AT gmail.com>
|
||||
# Copyright (C) 2007-2008 Brendan Taylor <whateley AT gmail.com>
|
||||
# Julien Pivotto <roidelapluie AT gmail.com>
|
||||
# Stephan Erb <steve-e AT h3c.de>
|
||||
# Copyright (C) 2008 Jonathan Schleifer <js-gajim AT webkeks.org>
|
||||
# Copyright (C) 2016-2017 Emmanuel Gil Peyrot <linkmauve AT linkmauve.fr>
|
||||
# Philipp Hörist <philipp AT hoerist.com>
|
||||
#
|
||||
# This file is part of Gajim.
|
||||
#
|
||||
# Gajim is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published
|
||||
# by the Free Software Foundation; version 3 only.
|
||||
#
|
||||
# Gajim is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with Gajim. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import sys
|
||||
import os
|
||||
from datetime import datetime
|
||||
from pathlib import Path
|
||||
from urllib.parse import unquote
|
||||
|
||||
from gi.repository import GLib, Gio, Gtk
|
||||
import nbxmpp
|
||||
from gi.repository import Gio
|
||||
from gi.repository import GLib
|
||||
from gi.repository import Gtk
|
||||
|
||||
import gajim
|
||||
from gajim.common import app
|
||||
from gajim.common import i18n
|
||||
from gajim.common import ged
|
||||
from gajim.common import configpaths
|
||||
from gajim.common import logging_helpers
|
||||
from gajim.common import exceptions
|
||||
from gajim.common import caps_cache
|
||||
from gajim.common import logger
|
||||
from gajim.common.i18n import _
|
||||
|
||||
|
||||
class GajimApplication(Gtk.Application):
|
||||
'''Main class handling activation and command line.'''
|
||||
|
||||
def __init__(self):
|
||||
Gtk.Application.__init__(self, application_id='org.gajim.Gajim',
|
||||
flags=(
|
||||
Gio.ApplicationFlags.HANDLES_COMMAND_LINE |
|
||||
Gio.ApplicationFlags.HANDLES_OPEN))
|
||||
flags = (Gio.ApplicationFlags.HANDLES_COMMAND_LINE |
|
||||
Gio.ApplicationFlags.HANDLES_OPEN)
|
||||
Gtk.Application.__init__(self,
|
||||
application_id='org.gajim.Gajim',
|
||||
flags=flags)
|
||||
|
||||
self.add_main_option(
|
||||
'version',
|
||||
ord('V'),
|
||||
GLib.OptionFlags.NONE,
|
||||
GLib.OptionArg.NONE,
|
||||
_('Show the application\'s version'))
|
||||
|
||||
self.add_main_option(
|
||||
'quiet',
|
||||
ord('q'),
|
||||
GLib.OptionFlags.NONE,
|
||||
GLib.OptionArg.NONE,
|
||||
_('Show only critical errors'))
|
||||
|
||||
self.add_main_option(
|
||||
'separate',
|
||||
ord('s'),
|
||||
GLib.OptionFlags.NONE,
|
||||
GLib.OptionArg.NONE,
|
||||
_('Separate profile files completely '
|
||||
'(even history database and plugins)'))
|
||||
|
||||
self.add_main_option(
|
||||
'verbose',
|
||||
ord('v'),
|
||||
GLib.OptionFlags.NONE,
|
||||
GLib.OptionArg.NONE,
|
||||
_('Print XML stanzas and other debug information'))
|
||||
|
||||
self.add_main_option(
|
||||
'profile',
|
||||
ord('p'),
|
||||
GLib.OptionFlags.NONE,
|
||||
GLib.OptionArg.STRING,
|
||||
_('Use defined profile in configuration directory'),
|
||||
'NAME')
|
||||
|
||||
self.add_main_option(
|
||||
'config-path',
|
||||
ord('c'),
|
||||
GLib.OptionFlags.NONE,
|
||||
GLib.OptionArg.STRING,
|
||||
_('Set configuration directory'),
|
||||
'PATH')
|
||||
|
||||
self.add_main_option(
|
||||
'loglevel',
|
||||
ord('l'),
|
||||
GLib.OptionFlags.NONE,
|
||||
GLib.OptionArg.STRING,
|
||||
_('Configure logging system'),
|
||||
'LEVEL')
|
||||
|
||||
self.add_main_option(
|
||||
'warnings',
|
||||
ord('w'),
|
||||
GLib.OptionFlags.NONE,
|
||||
GLib.OptionArg.NONE,
|
||||
_('Show all warnings'))
|
||||
|
||||
self.add_main_option(
|
||||
'ipython',
|
||||
ord('i'),
|
||||
GLib.OptionFlags.NONE,
|
||||
GLib.OptionArg.NONE,
|
||||
_('Open IPython shell'))
|
||||
|
||||
self.add_main_option(
|
||||
'show-next-pending-event',
|
||||
0,
|
||||
GLib.OptionFlags.NONE,
|
||||
GLib.OptionArg.NONE,
|
||||
_('Pops up a window with the next pending event'))
|
||||
|
||||
self.add_main_option(
|
||||
'start-chat', 0,
|
||||
GLib.OptionFlags.NONE,
|
||||
GLib.OptionArg.NONE,
|
||||
_('Start a new chat'))
|
||||
|
||||
self.add_main_option(
|
||||
'simulate-network-lost',
|
||||
0,
|
||||
GLib.OptionFlags.NONE,
|
||||
GLib.OptionArg.NONE,
|
||||
_('Simulate loss of connectivity'))
|
||||
|
||||
self.add_main_option(
|
||||
'simulate-network-connected',
|
||||
0,
|
||||
GLib.OptionFlags.NONE,
|
||||
GLib.OptionArg.NONE,
|
||||
_('Simulate regaining connectivity'))
|
||||
|
||||
self.add_main_option('version', ord('V'), GLib.OptionFlags.NONE,
|
||||
GLib.OptionArg.NONE,
|
||||
_('Show the application\'s version'))
|
||||
self.add_main_option('quiet', ord('q'), GLib.OptionFlags.NONE,
|
||||
GLib.OptionArg.NONE,
|
||||
_('Show only critical errors'))
|
||||
self.add_main_option('separate', ord('s'), GLib.OptionFlags.NONE,
|
||||
GLib.OptionArg.NONE,
|
||||
_('Separate profile files completely (even '
|
||||
'history database and plugins)'))
|
||||
self.add_main_option('verbose', ord('v'), GLib.OptionFlags.NONE,
|
||||
GLib.OptionArg.NONE,
|
||||
_('Print XML stanzas and other debug '
|
||||
'information'))
|
||||
self.add_main_option('profile', ord('p'), GLib.OptionFlags.NONE,
|
||||
GLib.OptionArg.STRING,
|
||||
_('Use defined profile in configuration '
|
||||
'directory'), 'NAME')
|
||||
self.add_main_option('config-path', ord('c'), GLib.OptionFlags.NONE,
|
||||
GLib.OptionArg.STRING,
|
||||
_('Set configuration directory'), 'PATH')
|
||||
self.add_main_option('loglevel', ord('l'), GLib.OptionFlags.NONE,
|
||||
GLib.OptionArg.STRING,
|
||||
_('Configure logging system'), 'LEVEL')
|
||||
self.add_main_option('warnings', ord('w'), GLib.OptionFlags.NONE,
|
||||
GLib.OptionArg.NONE,
|
||||
_('Show all warnings'))
|
||||
self.add_main_option('ipython', ord('i'), GLib.OptionFlags.NONE,
|
||||
GLib.OptionArg.NONE,
|
||||
_('Open IPython shell'))
|
||||
self.add_main_option('show-next-pending-event', 0, GLib.OptionFlags.NONE,
|
||||
GLib.OptionArg.NONE,
|
||||
_('Pops up a window with the next pending event'))
|
||||
self.add_main_option('start-chat', 0, GLib.OptionFlags.NONE,
|
||||
GLib.OptionArg.NONE,
|
||||
_('Start a new chat'))
|
||||
|
||||
self.add_main_option_entries(self._get_remaining_entry())
|
||||
|
||||
|
|
@ -132,20 +193,24 @@ class GajimApplication(Gtk.Application):
|
|||
caps_cache.initialize(app.logger)
|
||||
except exceptions.DatabaseMalformed as error:
|
||||
dlg = Gtk.MessageDialog(
|
||||
None,
|
||||
Gtk.DialogFlags.DESTROY_WITH_PARENT | Gtk.DialogFlags.MODAL,
|
||||
Gtk.MessageType.ERROR,
|
||||
Gtk.ButtonsType.OK,
|
||||
_('Database Error'))
|
||||
transient_for=None,
|
||||
destroy_with_parent=True,
|
||||
modal=True,
|
||||
message_type=Gtk.MessageType.ERROR,
|
||||
buttons=Gtk.ButtonsType.OK,
|
||||
text=_('Database Error'))
|
||||
dlg.format_secondary_text(str(error))
|
||||
dlg.run()
|
||||
dlg.destroy()
|
||||
sys.exit()
|
||||
|
||||
from gajim.gtk.util import load_user_iconsets
|
||||
load_user_iconsets()
|
||||
|
||||
# Set Application Menu
|
||||
app.app = self
|
||||
from gajim import gtkgui_helpers
|
||||
builder = gtkgui_helpers.get_gtk_builder('application_menu.ui')
|
||||
from gajim.gtk.util import get_builder
|
||||
builder = get_builder('application_menu.ui')
|
||||
menubar = builder.get_object("menubar")
|
||||
appmenu = builder.get_object("appmenu")
|
||||
if app.prefers_app_menu():
|
||||
|
|
@ -166,6 +231,10 @@ class GajimApplication(Gtk.Application):
|
|||
from gajim import gui_menu_builder
|
||||
gui_menu_builder.build_accounts_menu()
|
||||
|
||||
app.ged.register_event_handler('feature-discovered',
|
||||
ged.CORE,
|
||||
self._on_feature_discovered)
|
||||
|
||||
def _open_uris(self, uris):
|
||||
for uri in uris:
|
||||
app.log('uri_handler').info('open %s', uri)
|
||||
|
|
@ -186,11 +255,12 @@ class GajimApplication(Gtk.Application):
|
|||
attributes = cmd.split(';')
|
||||
message = None
|
||||
for key in attributes:
|
||||
if key.startswith('body'):
|
||||
try:
|
||||
message = unquote(key.split('=')[1])
|
||||
except Exception:
|
||||
app.log('uri_handler').error('Invalid URI: %s', cmd)
|
||||
if not key.startswith('body'):
|
||||
continue
|
||||
try:
|
||||
message = unquote(key.split('=')[1])
|
||||
except Exception:
|
||||
app.log('uri_handler').error('Invalid URI: %s', cmd)
|
||||
accounts = list(app.connections.keys())
|
||||
if not accounts:
|
||||
continue
|
||||
|
|
@ -214,10 +284,11 @@ class GajimApplication(Gtk.Application):
|
|||
# Parse all options that should be executed on a remote instance
|
||||
options = command_line.get_options_dict()
|
||||
|
||||
remote_commands = ['ipython',
|
||||
'show-next-pending-event',
|
||||
'start-chat',
|
||||
]
|
||||
remote_commands = [
|
||||
'ipython',
|
||||
'show-next-pending-event',
|
||||
'start-chat',
|
||||
]
|
||||
|
||||
remaining = options.lookup_value(GLib.OPTION_REMAINING,
|
||||
GLib.VariantType.new('as'))
|
||||
|
|
@ -227,6 +298,14 @@ class GajimApplication(Gtk.Application):
|
|||
self.activate_action(cmd)
|
||||
return 0
|
||||
|
||||
if options.contains('simulate-network-lost'):
|
||||
app.interface.network_status_changed(None, False)
|
||||
return 0
|
||||
|
||||
if options.contains('simulate-network-connected'):
|
||||
app.interface.network_status_changed(None, True)
|
||||
return 0
|
||||
|
||||
if remaining is not None:
|
||||
self._open_uris(remaining.unpack())
|
||||
return 0
|
||||
|
|
@ -234,9 +313,13 @@ class GajimApplication(Gtk.Application):
|
|||
self.activate()
|
||||
return 0
|
||||
|
||||
def _handle_local_options(self, application,
|
||||
def _handle_local_options(self,
|
||||
application: Gtk.Application,
|
||||
options: GLib.VariantDict) -> int:
|
||||
# Parse all options that have to be executed before ::startup
|
||||
if options.contains('version'):
|
||||
print(gajim.__version__)
|
||||
return 0
|
||||
if options.contains('profile'):
|
||||
# Incorporate profile name into application id
|
||||
# to have a single app instance for each profile.
|
||||
|
|
@ -251,7 +334,14 @@ class GajimApplication(Gtk.Application):
|
|||
configpaths.set_config_root(path)
|
||||
|
||||
configpaths.init()
|
||||
logging_helpers.init()
|
||||
|
||||
if app.get_win_debug_mode():
|
||||
# Redirect has to happen before logging init
|
||||
self._redirect_output()
|
||||
logging_helpers.init()
|
||||
logging_helpers.set_verbose()
|
||||
else:
|
||||
logging_helpers.init()
|
||||
|
||||
if options.contains('quiet'):
|
||||
logging_helpers.set_quiet()
|
||||
|
|
@ -278,6 +368,14 @@ class GajimApplication(Gtk.Application):
|
|||
warnings.showwarning = warn_with_traceback
|
||||
warnings.filterwarnings(action="always")
|
||||
|
||||
@staticmethod
|
||||
def _redirect_output():
|
||||
debug_folder = Path(configpaths.get('DEBUG'))
|
||||
date = datetime.today().strftime('%Y-%m-%d')
|
||||
filename = '%s-debug.log' % date
|
||||
fd = open(debug_folder / filename, 'a')
|
||||
sys.stderr = sys.stdout = fd
|
||||
|
||||
def add_actions(self):
|
||||
''' Build Application Actions '''
|
||||
from gajim import app_actions
|
||||
|
|
@ -290,11 +388,6 @@ class GajimApplication(Gtk.Application):
|
|||
act.connect('change-state', app_actions.on_merge_accounts)
|
||||
self.add_action(act)
|
||||
|
||||
act = Gio.SimpleAction.new_stateful(
|
||||
'agent', None,
|
||||
GLib.Variant.new_boolean(app.config.get('use_gpg_agent')))
|
||||
self.add_action(act)
|
||||
|
||||
# General Actions
|
||||
|
||||
general_actions = [
|
||||
|
|
@ -323,6 +416,19 @@ class GajimApplication(Gtk.Application):
|
|||
act.connect("activate", app_actions.on_add_contact_jid)
|
||||
self.add_action(act)
|
||||
|
||||
act = Gio.SimpleAction.new('copy-text', GLib.VariantType.new('s'))
|
||||
act.connect("activate", app_actions.copy_text)
|
||||
self.add_action(act)
|
||||
|
||||
act = Gio.SimpleAction.new('open-link', GLib.VariantType.new('s'))
|
||||
act.connect("activate", app_actions.open_link)
|
||||
self.add_action(act)
|
||||
|
||||
act = Gio.SimpleAction.new('browse-history',
|
||||
GLib.VariantType.new('a{sv}'))
|
||||
act.connect("activate", app_actions.on_browse_history)
|
||||
self.add_action(act)
|
||||
|
||||
for action in general_actions:
|
||||
action_name, func = action
|
||||
act = Gio.SimpleAction.new(action_name, None)
|
||||
|
|
@ -339,33 +445,33 @@ class GajimApplication(Gtk.Application):
|
|||
self.add_account_actions(accounts_list[0])
|
||||
|
||||
def _get_account_actions(self, account):
|
||||
from gajim import app_actions
|
||||
from gajim import app_actions as a
|
||||
|
||||
if account == 'Local':
|
||||
return [
|
||||
('-xml-console', app_actions.on_xml_console, 'always', 's')
|
||||
('-xml-console', a.on_xml_console, 'always', 's')
|
||||
]
|
||||
|
||||
return [
|
||||
('-start-single-chat', app_actions.on_single_message, 'online', 's'),
|
||||
('-join-groupchat', app_actions.on_join_gc, 'online', 's'),
|
||||
('-add-contact', app_actions.on_add_contact, 'online', 's'),
|
||||
('-services', app_actions.on_service_disco, 'online', 's'),
|
||||
('-profile', app_actions.on_profile, 'feature', 's'),
|
||||
('-xml-console', app_actions.on_xml_console, 'always', 's'),
|
||||
('-server-info', app_actions.on_server_info, 'online', 's'),
|
||||
('-archive', app_actions.on_mam_preferences, 'feature', 's'),
|
||||
('-sync-history', app_actions.on_history_sync, 'online', 's'),
|
||||
('-privacylists', app_actions.on_privacy_lists, 'feature', 's'),
|
||||
('-send-server-message',
|
||||
app_actions.on_send_server_message, 'online', 's'),
|
||||
('-set-motd', app_actions.on_set_motd, 'online', 's'),
|
||||
('-update-motd', app_actions.on_update_motd, 'online', 's'),
|
||||
('-delete-motd', app_actions.on_delete_motd, 'online', 's'),
|
||||
('-activate-bookmark',
|
||||
app_actions.on_activate_bookmark, 'online', 'a{sv}'),
|
||||
('-open-event', app_actions.on_open_event, 'always', 'a{sv}'),
|
||||
('-import-contacts', app_actions.on_import_contacts, 'online', 's'),
|
||||
('-start-single-chat', a.on_single_message, 'online', 's'),
|
||||
('-start-chat', a.start_chat, 'online', 'as'),
|
||||
('-join-groupchat', a.on_join_gc, 'online', 'as'),
|
||||
('-add-contact', a.on_add_contact, 'online', 'as'),
|
||||
('-services', a.on_service_disco, 'online', 's'),
|
||||
('-profile', a.on_profile, 'feature', 's'),
|
||||
('-xml-console', a.on_xml_console, 'always', 's'),
|
||||
('-server-info', a.on_server_info, 'online', 's'),
|
||||
('-archive', a.on_mam_preferences, 'feature', 's'),
|
||||
('-sync-history', a.on_history_sync, 'online', 's'),
|
||||
('-privacylists', a.on_privacy_lists, 'feature', 's'),
|
||||
('-blocking', a.on_blocking_list, 'feature', 's'),
|
||||
('-send-server-message', a.on_send_server_message, 'online', 's'),
|
||||
('-set-motd', a.on_set_motd, 'online', 's'),
|
||||
('-update-motd', a.on_update_motd, 'online', 's'),
|
||||
('-delete-motd', a.on_delete_motd, 'online', 's'),
|
||||
('-activate-bookmark', a.on_activate_bookmark, 'online', 'a{sv}'),
|
||||
('-open-event', a.on_open_event, 'always', 'a{sv}'),
|
||||
('-import-contacts', a.on_import_contacts, 'online', 's'),
|
||||
]
|
||||
|
||||
def add_account_actions(self, account):
|
||||
|
|
@ -396,3 +502,17 @@ class GajimApplication(Gtk.Application):
|
|||
elif new_state and state == 'online':
|
||||
# We go online
|
||||
self.lookup_action(account + action_name).set_enabled(True)
|
||||
|
||||
def _on_feature_discovered(self, event):
|
||||
if event.feature == nbxmpp.NS_VCARD:
|
||||
action = '%s-profile' % event.account
|
||||
self.lookup_action(action).set_enabled(True)
|
||||
elif event.feature in (nbxmpp.NS_MAM_1, nbxmpp.NS_MAM_2):
|
||||
action = '%s-archive' % event.account
|
||||
self.lookup_action(action).set_enabled(True)
|
||||
elif event.feature == nbxmpp.NS_PRIVACY:
|
||||
action = '%s-privacylists' % event.account
|
||||
self.lookup_action(action).set_enabled(True)
|
||||
elif event.feature == nbxmpp.NS_BLOCKING:
|
||||
action = '%s-blocking' % event.account
|
||||
self.lookup_action(action).set_enabled(True)
|
||||
|
|
|
|||
|
|
@ -1,152 +0,0 @@
|
|||
# -*- coding:utf-8 -*-
|
||||
## src/atom_window.py
|
||||
##
|
||||
## Copyright (C) 2006 Tomasz Melcer <liori AT exroot.org>
|
||||
## Copyright (C) 2006-2014 Yann Leboulanger <asterix AT lagaule.org>
|
||||
## Copyright (C) 2007 Nikos Kouremenos <kourem AT gmail.com>
|
||||
## Copyright (C) 2008 Jonathan Schleifer <js-gajim AT webkeks.org>
|
||||
##
|
||||
## This file is part of Gajim.
|
||||
##
|
||||
## Gajim is free software; you can redistribute it and/or modify
|
||||
## it under the terms of the GNU General Public License as published
|
||||
## by the Free Software Foundation; version 3 only.
|
||||
##
|
||||
## Gajim is distributed in the hope that it will be useful,
|
||||
## but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
## GNU General Public License for more details.
|
||||
##
|
||||
## You should have received a copy of the GNU General Public License
|
||||
## along with Gajim. If not, see <http://www.gnu.org/licenses/>.
|
||||
##
|
||||
|
||||
|
||||
from gi.repository import Gdk
|
||||
from gi.repository import GLib
|
||||
|
||||
from gajim import gtkgui_helpers
|
||||
from gajim.common import helpers
|
||||
from gajim.common import i18n
|
||||
|
||||
class AtomWindow:
|
||||
window = None
|
||||
entries = []
|
||||
|
||||
@classmethod
|
||||
def newAtomEntry(cls, entry):
|
||||
"""
|
||||
Queue new entry, open window if there's no one opened
|
||||
"""
|
||||
cls.entries.append(entry)
|
||||
|
||||
if cls.window is None:
|
||||
cls.window = AtomWindow()
|
||||
else:
|
||||
cls.window.updateCounter()
|
||||
|
||||
@classmethod
|
||||
def windowClosed(cls):
|
||||
cls.window = None
|
||||
|
||||
def __init__(self):
|
||||
"""
|
||||
Create new window... only if we have anything to show
|
||||
"""
|
||||
assert len(self.__class__.entries)
|
||||
|
||||
self.entry = None # the entry actually displayed
|
||||
|
||||
self.xml = gtkgui_helpers.get_gtk_builder('atom_entry_window.ui')
|
||||
self.window = self.xml.get_object('atom_entry_window')
|
||||
for name in ('new_entry_label', 'feed_title_label',
|
||||
'feed_title_eventbox', 'feed_tagline_label', 'entry_title_label',
|
||||
'entry_title_eventbox', 'last_modified_label', 'close_button',
|
||||
'next_button'):
|
||||
self.__dict__[name] = self.xml.get_object(name)
|
||||
|
||||
self.displayNextEntry()
|
||||
|
||||
self.xml.connect_signals(self)
|
||||
self.window.show_all()
|
||||
|
||||
self.entry_title_eventbox.add_events(Gdk.EventMask.BUTTON_PRESS_MASK)
|
||||
self.feed_title_eventbox.add_events(Gdk.EventMask.BUTTON_PRESS_MASK)
|
||||
|
||||
def displayNextEntry(self):
|
||||
"""
|
||||
Get next entry from the queue and display it in the window
|
||||
"""
|
||||
assert len(self.__class__.entries)>0
|
||||
|
||||
newentry = self.__class__.entries.pop(0)
|
||||
|
||||
# fill the fields
|
||||
if newentry.feed_link is not None:
|
||||
self.feed_title_label.set_markup(
|
||||
'<span foreground="blue" underline="single">%s</span>' % \
|
||||
GLib.markup_escape_text(newentry.feed_title))
|
||||
else:
|
||||
self.feed_title_label.set_markup(GLib.markup_escape_text(
|
||||
newentry.feed_title))
|
||||
|
||||
self.feed_tagline_label.set_markup(
|
||||
'<small>%s</small>' % GLib.markup_escape_text(
|
||||
newentry.feed_tagline))
|
||||
|
||||
if newentry.title:
|
||||
if newentry.uri is not None:
|
||||
self.entry_title_label.set_markup(
|
||||
'<span foreground="blue" underline="single">%s</span>' % \
|
||||
GLib.markup_escape_text(newentry.title))
|
||||
else:
|
||||
self.entry_title_label.set_markup(GLib.markup_escape_text(
|
||||
newentry.title))
|
||||
else:
|
||||
self.entry_title_label.set_markup('')
|
||||
|
||||
self.last_modified_label.set_text(newentry.updated)
|
||||
|
||||
# update the counters
|
||||
self.updateCounter()
|
||||
|
||||
self.entry = newentry
|
||||
|
||||
def updateCounter(self):
|
||||
"""
|
||||
Display number of events on the top of window, sometimes it needs to be
|
||||
changed
|
||||
"""
|
||||
count = len(self.__class__.entries)
|
||||
if count:
|
||||
self.new_entry_label.set_text(i18n.ngettext(
|
||||
'You have received new entries (and %d not displayed):',
|
||||
'You have received new entries (and %d not displayed):', count,
|
||||
count, count))
|
||||
self.next_button.set_sensitive(True)
|
||||
else:
|
||||
self.new_entry_label.set_text(_('You have received new entry:'))
|
||||
self.next_button.set_sensitive(False)
|
||||
|
||||
def on_close_button_clicked(self, widget):
|
||||
self.window.destroy()
|
||||
self.windowClosed()
|
||||
|
||||
def on_next_button_clicked(self, widget):
|
||||
self.displayNextEntry()
|
||||
|
||||
def on_entry_title_eventbox_button_press_event(self, widget, event):
|
||||
#FIXME: make it using special gtk2.10 widget
|
||||
if event.button == 1: # left click
|
||||
uri = self.entry.uri
|
||||
if uri is not None:
|
||||
helpers.launch_browser_mailer('url', uri)
|
||||
return True
|
||||
|
||||
def on_feed_title_eventbox_button_press_event(self, widget, event):
|
||||
#FIXME: make it using special gtk2.10 widget
|
||||
if event.button == 1: # left click
|
||||
uri = self.entry.feed_uri
|
||||
if uri is not None:
|
||||
helpers.launch_browser_mailer('url', uri)
|
||||
return True
|
||||
|
|
@ -1,159 +0,0 @@
|
|||
# -*- coding:utf-8 -*-
|
||||
## src/cell_renderer_image.py
|
||||
##
|
||||
## Copyright (C) 2003-2014 Yann Leboulanger <asterix AT lagaule.org>
|
||||
## Copyright (C) 2005 Vincent Hanquez <tab AT snarc.org>
|
||||
## Copyright (C) 2005-2007 Nikos Kouremenos <kourem AT gmail.com>
|
||||
## Copyright (C) 2006 Travis Shirk <travis AT pobox.com>
|
||||
##
|
||||
## This file is part of Gajim.
|
||||
##
|
||||
## Gajim is free software; you can redistribute it and/or modify
|
||||
## it under the terms of the GNU General Public License as published
|
||||
## by the Free Software Foundation; version 3 only.
|
||||
##
|
||||
## Gajim is distributed in the hope that it will be useful,
|
||||
## but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
## GNU General Public License for more details.
|
||||
##
|
||||
## You should have received a copy of the GNU General Public License
|
||||
## along with Gajim. If not, see <http://www.gnu.org/licenses/>.
|
||||
##
|
||||
|
||||
from gi.repository import GLib
|
||||
from gi.repository import Gtk
|
||||
from gi.repository import Gdk
|
||||
from gi.repository import GObject
|
||||
|
||||
class CellRendererImage(Gtk.CellRendererPixbuf):
|
||||
|
||||
__gproperties__ = {
|
||||
'image': (GObject.TYPE_OBJECT, 'Image',
|
||||
'Image', GObject.ParamFlags.READWRITE),
|
||||
}
|
||||
|
||||
def __init__(self, col_index, tv_index):
|
||||
super(CellRendererImage, self).__init__()
|
||||
self.image = None
|
||||
self.col_index = col_index
|
||||
self.tv_index = tv_index
|
||||
self.iters = {}
|
||||
|
||||
def do_set_property(self, pspec, value):
|
||||
setattr(self, pspec.name, value)
|
||||
|
||||
def do_get_property(self, pspec):
|
||||
return getattr(self, pspec.name)
|
||||
|
||||
def do_activate(event, widget, path, bg_area, cell_area, flags):
|
||||
"""Renderers cannot be activated; always return True."""
|
||||
return True
|
||||
|
||||
def do_editing_started(event, widget, path, fb_area, cell_area, flags):
|
||||
"""Renderers cannot be edited; always return None."""
|
||||
return None
|
||||
|
||||
def func(self, model, path, iter_, image_tree):
|
||||
image, tree = image_tree
|
||||
if model.get_value(iter_, self.tv_index) != image:
|
||||
return
|
||||
self.redraw = 1
|
||||
col = tree.get_column(self.col_index)
|
||||
cell_area = tree.get_cell_area(path, col)
|
||||
|
||||
tree.queue_draw_area(cell_area.x, cell_area.y, cell_area.width,
|
||||
cell_area.height)
|
||||
|
||||
def animation_timeout(self, tree, image):
|
||||
if image.get_storage_type() != Gtk.ImageType.ANIMATION:
|
||||
return
|
||||
self.redraw = 0
|
||||
iter_ = self.iters[image]
|
||||
timeval = GLib.TimeVal()
|
||||
timeval.tv_sec = GLib.get_monotonic_time() / 1000000
|
||||
iter_.advance(timeval)
|
||||
model = tree.get_model()
|
||||
if model:
|
||||
model.foreach(self.func, (image, tree))
|
||||
if self.redraw:
|
||||
GLib.timeout_add(iter_.get_delay_time(),
|
||||
self.animation_timeout, tree, image)
|
||||
elif image in self.iters:
|
||||
del self.iters[image]
|
||||
|
||||
def do_render(self, ctx, widget, background_area, cell_area, flags):
|
||||
if not self.image:
|
||||
return
|
||||
|
||||
if self.image.get_storage_type() == Gtk.ImageType.ANIMATION:
|
||||
if self.image not in self.iters:
|
||||
if not isinstance(widget, Gtk.TreeView):
|
||||
return
|
||||
animation = self.image.get_animation()
|
||||
timeval = GLib.TimeVal()
|
||||
timeval.tv_sec = GLib.get_monotonic_time() / 1000000
|
||||
iter_ = animation.get_iter(timeval)
|
||||
self.iters[self.image] = iter_
|
||||
GLib.timeout_add(iter_.get_delay_time(), self.animation_timeout,
|
||||
widget, self.image)
|
||||
|
||||
pix = self.iters[self.image].get_pixbuf()
|
||||
elif self.image.get_storage_type() == Gtk.ImageType.PIXBUF:
|
||||
pix = self.image.get_pixbuf()
|
||||
else:
|
||||
return
|
||||
|
||||
calc_width = self.get_property('xpad') * 2 + pix.get_width()
|
||||
calc_height = self.get_property('ypad') * 2 + pix.get_height()
|
||||
|
||||
x_pos = cell_area.x + self.get_property('xalign') * \
|
||||
(cell_area.width - calc_width - self.get_property('xpad'))
|
||||
y_pos = cell_area.y + self.get_property('yalign') * \
|
||||
(cell_area.height - calc_height - self.get_property('ypad'))
|
||||
Gdk.cairo_set_source_pixbuf(ctx, pix, x_pos, y_pos)
|
||||
ctx.paint()
|
||||
|
||||
def do_get_preferred_height(self, widget):
|
||||
"""
|
||||
Return the height we need for this cell.
|
||||
|
||||
Each cell is drawn individually and is only as wide as it needs
|
||||
to be, we let the TreeViewColumn take care of making them all
|
||||
line up.
|
||||
"""
|
||||
if not self.image:
|
||||
return 0, 0
|
||||
if self.image.get_storage_type() == Gtk.ImageType.ANIMATION:
|
||||
animation = self.image.get_animation()
|
||||
timeval = GLib.TimeVal()
|
||||
timeval.tv_sec = GLib.get_monotonic_time() / 1000000
|
||||
pix = animation.get_iter(timeval).get_pixbuf()
|
||||
elif self.image.get_storage_type() == Gtk.ImageType.PIXBUF:
|
||||
pix = self.image.get_pixbuf()
|
||||
else:
|
||||
return 0, 0, 0, 0
|
||||
calc_height = self.get_property('ypad') * 2 + pix.get_height()
|
||||
return calc_height, calc_height
|
||||
|
||||
def do_get_preferred_width(self, widget):
|
||||
"""
|
||||
Return the width we need for this cell.
|
||||
|
||||
Each cell is drawn individually and is only as wide as it needs
|
||||
to be, we let the TreeViewColumn take care of making them all
|
||||
line up.
|
||||
"""
|
||||
if not self.image:
|
||||
return 0, 0
|
||||
if self.image.get_storage_type() == Gtk.ImageType.ANIMATION:
|
||||
animation = self.image.get_animation()
|
||||
timeval = GLib.TimeVal()
|
||||
timeval.tv_sec = GLib.get_monotonic_time() / 1000000
|
||||
pix = animation.get_iter(timeval).get_pixbuf()
|
||||
elif self.image.get_storage_type() == Gtk.ImageType.PIXBUF:
|
||||
pix = self.image.get_pixbuf()
|
||||
else:
|
||||
return 0, 0, 0, 0
|
||||
calc_width = self.get_property('xpad') * 2 + pix.get_width()
|
||||
return calc_width, calc_width
|
||||
|
|
@ -1,58 +1,75 @@
|
|||
# -*- coding:utf-8 -*-
|
||||
## src/chat_control.py
|
||||
##
|
||||
## Copyright (C) 2006 Dimitur Kirov <dkirov AT gmail.com>
|
||||
## Copyright (C) 2006-2014 Yann Leboulanger <asterix AT lagaule.org>
|
||||
## Copyright (C) 2006-2008 Jean-Marie Traissard <jim AT lapin.org>
|
||||
## Nikos Kouremenos <kourem AT gmail.com>
|
||||
## Travis Shirk <travis AT pobox.com>
|
||||
## Copyright (C) 2007 Lukas Petrovicky <lukas AT petrovicky.net>
|
||||
## Julien Pivotto <roidelapluie AT gmail.com>
|
||||
## Copyright (C) 2007-2008 Brendan Taylor <whateley AT gmail.com>
|
||||
## Stephan Erb <steve-e AT h3c.de>
|
||||
## Copyright (C) 2008 Jonathan Schleifer <js-gajim AT webkeks.org>
|
||||
##
|
||||
## This file is part of Gajim.
|
||||
##
|
||||
## Gajim is free software; you can redistribute it and/or modify
|
||||
## it under the terms of the GNU General Public License as published
|
||||
## by the Free Software Foundation; version 3 only.
|
||||
##
|
||||
## Gajim is distributed in the hope that it will be useful,
|
||||
## but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
## GNU General Public License for more details.
|
||||
##
|
||||
## You should have received a copy of the GNU General Public License
|
||||
## along with Gajim. If not, see <http://www.gnu.org/licenses/>.
|
||||
##
|
||||
# Copyright (C) 2006 Dimitur Kirov <dkirov AT gmail.com>
|
||||
# Copyright (C) 2006-2014 Yann Leboulanger <asterix AT lagaule.org>
|
||||
# Copyright (C) 2006-2008 Jean-Marie Traissard <jim AT lapin.org>
|
||||
# Nikos Kouremenos <kourem AT gmail.com>
|
||||
# Travis Shirk <travis AT pobox.com>
|
||||
# Copyright (C) 2007 Lukas Petrovicky <lukas AT petrovicky.net>
|
||||
# Julien Pivotto <roidelapluie AT gmail.com>
|
||||
# Copyright (C) 2007-2008 Brendan Taylor <whateley AT gmail.com>
|
||||
# Stephan Erb <steve-e AT h3c.de>
|
||||
# Copyright (C) 2008 Jonathan Schleifer <js-gajim AT webkeks.org>
|
||||
#
|
||||
# This file is part of Gajim.
|
||||
#
|
||||
# Gajim is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published
|
||||
# by the Free Software Foundation; version 3 only.
|
||||
#
|
||||
# Gajim is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with Gajim. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
from typing import ClassVar # pylint: disable=unused-import
|
||||
from typing import Type # pylint: disable=unused-import
|
||||
|
||||
import os
|
||||
import time
|
||||
|
||||
from gi.repository import Gtk
|
||||
from gi.repository import Gio
|
||||
from gi.repository import Pango
|
||||
from gi.repository import GLib
|
||||
from gajim import gtkgui_helpers
|
||||
from gajim import gui_menu_builder
|
||||
from gajim import message_control
|
||||
from gajim import dialogs
|
||||
from gajim.gtk import ConfirmationDialog
|
||||
from gajim.gtk import AddNewContactWindow
|
||||
from nbxmpp.protocol import NS_XHTML, NS_XHTML_IM, NS_FILE, NS_MUC
|
||||
from nbxmpp.protocol import NS_JINGLE_RTP_AUDIO, NS_JINGLE_RTP_VIDEO
|
||||
from nbxmpp.protocol import NS_JINGLE_ICE_UDP, NS_JINGLE_FILE_TRANSFER_5
|
||||
|
||||
from gajim.common import app
|
||||
from gajim.common import helpers
|
||||
from gajim.common import ged
|
||||
from gajim.common import i18n
|
||||
from gajim.common.i18n import _
|
||||
from gajim.common.helpers import AdditionalDataDict
|
||||
from gajim.common.helpers import open_uri
|
||||
from gajim.common.helpers import geo_provider_from_location
|
||||
from gajim.common.contacts import GC_Contact
|
||||
from nbxmpp.protocol import NS_XHTML, NS_XHTML_IM, NS_FILE, NS_MUC
|
||||
from nbxmpp.protocol import NS_JINGLE_RTP_AUDIO, NS_JINGLE_RTP_VIDEO
|
||||
from nbxmpp.protocol import NS_JINGLE_ICE_UDP, NS_JINGLE_FILE_TRANSFER_5
|
||||
from nbxmpp.protocol import NS_CHATSTATES
|
||||
from gajim.common.connection_handlers_events import MessageOutgoingEvent
|
||||
from gajim.common.const import AvatarSize, KindConstant
|
||||
from gajim.common.const import AvatarSize
|
||||
from gajim.common.const import KindConstant
|
||||
from gajim.common.const import Chatstate
|
||||
from gajim.common.const import PEPEventType
|
||||
|
||||
from gajim import gtkgui_helpers
|
||||
from gajim import gui_menu_builder
|
||||
from gajim import message_control
|
||||
from gajim import dialogs
|
||||
|
||||
from gajim.gtk.dialogs import NewConfirmationDialog
|
||||
from gajim.gtk.dialogs import DialogButton
|
||||
from gajim.gtk.add_contact import AddNewContactWindow
|
||||
from gajim.gtk.util import get_icon_name
|
||||
from gajim.gtk.util import get_cursor
|
||||
from gajim.gtk.util import ensure_proper_control
|
||||
from gajim.gtk.util import format_mood
|
||||
from gajim.gtk.util import format_activity
|
||||
from gajim.gtk.util import format_tune
|
||||
from gajim.gtk.util import format_location
|
||||
from gajim.gtk.util import get_activity_icon_name
|
||||
|
||||
from gajim.command_system.implementation.hosts import ChatCommands
|
||||
from gajim.command_system.framework import CommandHost # pylint: disable=unused-import
|
||||
from gajim.chat_control_base import ChatControlBase
|
||||
|
||||
################################################################################
|
||||
|
|
@ -73,7 +90,7 @@ class ChatControl(ChatControlBase):
|
|||
|
||||
# Set a command host to bound to. Every command given through a chat will be
|
||||
# processed with this command host.
|
||||
COMMAND_HOST = ChatCommands
|
||||
COMMAND_HOST = ChatCommands # type: ClassVar[Type[CommandHost]]
|
||||
|
||||
def __init__(self, parent_win, contact, acct, session, resource=None):
|
||||
ChatControlBase.__init__(self, self.TYPE_ID, parent_win,
|
||||
|
|
@ -105,7 +122,7 @@ class ChatControl(ChatControlBase):
|
|||
|
||||
# Menu for the HeaderBar
|
||||
self.control_menu = gui_menu_builder.get_singlechat_menu(
|
||||
self.control_id)
|
||||
self.control_id, self.account, self.contact.jid)
|
||||
settings_menu = self.xml.get_object('settings_menu')
|
||||
settings_menu.set_menu_model(self.control_menu)
|
||||
|
||||
|
|
@ -119,22 +136,10 @@ class ChatControl(ChatControlBase):
|
|||
self.video_available = False
|
||||
|
||||
self.update_toolbar()
|
||||
|
||||
self._pep_images = {}
|
||||
self._pep_images['mood'] = self.xml.get_object('mood_image')
|
||||
self._pep_images['activity'] = self.xml.get_object('activity_image')
|
||||
self._pep_images['tune'] = self.xml.get_object('tune_image')
|
||||
self._pep_images['geoloc'] = self.xml.get_object('location_image')
|
||||
self.update_all_pep_types()
|
||||
|
||||
self.show_avatar()
|
||||
|
||||
# Hook up signals
|
||||
message_tv_buffer = self.msg_textview.get_buffer()
|
||||
id_ = message_tv_buffer.connect('changed',
|
||||
self._on_message_tv_buffer_changed)
|
||||
self.handlers[id_] = message_tv_buffer
|
||||
|
||||
widget = self.xml.get_object('avatar_eventbox')
|
||||
widget.set_property('height-request', AvatarSize.CHAT)
|
||||
|
||||
|
|
@ -160,16 +165,6 @@ class ChatControl(ChatControlBase):
|
|||
id_ = widget.connect('released', self.on_num_button_released)
|
||||
self.handlers[id_] = widget
|
||||
|
||||
self.dtmf_window = self.xml.get_object('dtmf_window')
|
||||
self.dtmf_window.get_child().set_direction(Gtk.TextDirection.LTR)
|
||||
id_ = self.dtmf_window.connect('focus-out-event',
|
||||
self.on_dtmf_window_focus_out_event)
|
||||
self.handlers[id_] = self.dtmf_window
|
||||
|
||||
widget = self.xml.get_object('dtmf_button')
|
||||
id_ = widget.connect('clicked', self.on_dtmf_button_clicked)
|
||||
self.handlers[id_] = widget
|
||||
|
||||
widget = self.xml.get_object('mic_hscale')
|
||||
id_ = widget.connect('value_changed', self.on_mic_hscale_value_changed)
|
||||
self.handlers[id_] = widget
|
||||
|
|
@ -203,7 +198,7 @@ class ChatControl(ChatControlBase):
|
|||
session = app.connections[self.account].find_controlless_session(
|
||||
self.contact.jid, resource)
|
||||
|
||||
self.setup_seclabel(self.xml.get_object('label_selector'))
|
||||
self.setup_seclabel()
|
||||
if session:
|
||||
session.control = self
|
||||
self.session = session
|
||||
|
|
@ -223,8 +218,18 @@ class ChatControl(ChatControlBase):
|
|||
self.restore_conversation()
|
||||
self.msg_textview.grab_focus()
|
||||
|
||||
app.ged.register_event_handler('pep-received', ged.GUI1,
|
||||
self._nec_pep_received)
|
||||
app.ged.register_event_handler('nickname-received', ged.GUI1,
|
||||
self._on_nickname_received)
|
||||
app.ged.register_event_handler('mood-received', ged.GUI1,
|
||||
self._on_mood_received)
|
||||
app.ged.register_event_handler('activity-received', ged.GUI1,
|
||||
self._on_activity_received)
|
||||
app.ged.register_event_handler('tune-received', ged.GUI1,
|
||||
self._on_tune_received)
|
||||
app.ged.register_event_handler('location-received', ged.GUI1,
|
||||
self._on_location_received)
|
||||
app.ged.register_event_handler('update-client-info', ged.GUI1,
|
||||
self._on_update_client_info)
|
||||
if self.TYPE_ID == message_control.TYPE_CHAT:
|
||||
# Dont connect this when PrivateChatControl is used
|
||||
app.ged.register_event_handler('update-roster-avatar', ged.GUI1,
|
||||
|
|
@ -276,6 +281,17 @@ class ChatControl(ChatControlBase):
|
|||
self.video_action.connect('change-state', self._on_video)
|
||||
self.parent_win.window.add_action(self.video_action)
|
||||
|
||||
default_chatstate = app.config.get('send_chatstate_default')
|
||||
chatstate = app.config.get_per(
|
||||
'contacts', self.contact.jid, 'send_chatstate', default_chatstate)
|
||||
|
||||
act = Gio.SimpleAction.new_stateful(
|
||||
'send-chatstate-' + self.control_id,
|
||||
GLib.VariantType.new("s"),
|
||||
GLib.Variant("s", chatstate))
|
||||
act.connect('change-state', self._on_send_chatstate)
|
||||
self.parent_win.window.add_action(act)
|
||||
|
||||
def update_actions(self):
|
||||
win = self.parent_win.window
|
||||
online = app.account_is_connected(self.account)
|
||||
|
|
@ -363,6 +379,11 @@ class ChatControl(ChatControlBase):
|
|||
state = param.get_boolean()
|
||||
self.on_jingle_button_toggled(state, 'video')
|
||||
|
||||
def _on_send_chatstate(self, action, param):
|
||||
action.set_state(param)
|
||||
app.config.set_per('contacts', self.contact.jid,
|
||||
'send_chatstate', param.get_string())
|
||||
|
||||
def subscribe_events(self):
|
||||
"""
|
||||
Register listeners to the events class
|
||||
|
|
@ -386,12 +407,8 @@ class ChatControl(ChatControlBase):
|
|||
'Show a list of formattings'))
|
||||
else:
|
||||
self._formattings_button.set_sensitive(False)
|
||||
if self.contact.supports(NS_XHTML_IM):
|
||||
self._formattings_button.set_tooltip_text(_('Formatting is not '
|
||||
'available so long as GPG is active'))
|
||||
else:
|
||||
self._formattings_button.set_tooltip_text(_('This contact does '
|
||||
'not support HTML'))
|
||||
self._formattings_button.set_tooltip_text(
|
||||
_('This contact does not support HTML'))
|
||||
|
||||
# Jingle detection
|
||||
if self.contact.supports(NS_JINGLE_ICE_UDP) and \
|
||||
|
|
@ -405,35 +422,74 @@ class ChatControl(ChatControlBase):
|
|||
self.audio_available = False
|
||||
|
||||
def update_all_pep_types(self):
|
||||
for pep_type in self._pep_images:
|
||||
self.update_pep(pep_type)
|
||||
self._update_pep(PEPEventType.LOCATION)
|
||||
self._update_pep(PEPEventType.MOOD)
|
||||
self._update_pep(PEPEventType.ACTIVITY)
|
||||
self._update_pep(PEPEventType.TUNE)
|
||||
|
||||
def update_pep(self, pep_type):
|
||||
if isinstance(self.contact, GC_Contact):
|
||||
return
|
||||
if pep_type not in self._pep_images:
|
||||
return
|
||||
pep = self.contact.pep
|
||||
img = self._pep_images[pep_type]
|
||||
if pep_type in pep:
|
||||
img.set_from_pixbuf(gtkgui_helpers.get_pep_as_pixbuf(pep[pep_type]))
|
||||
img.set_tooltip_markup(pep[pep_type].asMarkupText())
|
||||
img.show()
|
||||
else:
|
||||
img.hide()
|
||||
|
||||
def _nec_pep_received(self, obj):
|
||||
if obj.conn.name != self.account:
|
||||
return
|
||||
if obj.jid != self.contact.jid:
|
||||
def _update_pep(self, type_):
|
||||
image = self._get_pep_widget(type_)
|
||||
data = self.contact.pep.get(type_)
|
||||
if data is None:
|
||||
image.hide()
|
||||
return
|
||||
|
||||
if obj.pep_type == 'nick':
|
||||
self.update_ui()
|
||||
self.parent_win.redraw_tab(self)
|
||||
self.parent_win.show_title()
|
||||
else:
|
||||
self.update_pep(obj.pep_type)
|
||||
if type_ == PEPEventType.MOOD:
|
||||
icon = 'mood-%s' % data.mood
|
||||
formated_text = format_mood(*data)
|
||||
elif type_ == PEPEventType.ACTIVITY:
|
||||
icon = get_activity_icon_name(data.activity, data.subactivity)
|
||||
formated_text = format_activity(*data)
|
||||
elif type_ == PEPEventType.TUNE:
|
||||
icon = 'audio-x-generic'
|
||||
formated_text = format_tune(*data)
|
||||
elif type_ == PEPEventType.LOCATION:
|
||||
icon = 'applications-internet'
|
||||
formated_text = format_location(data)
|
||||
|
||||
image.set_from_icon_name(icon, Gtk.IconSize.MENU)
|
||||
image.set_tooltip_markup(formated_text)
|
||||
image.show()
|
||||
|
||||
def _get_pep_widget(self, type_):
|
||||
if type_ == PEPEventType.MOOD:
|
||||
return self.xml.get_object('mood_image')
|
||||
if type_ == PEPEventType.ACTIVITY:
|
||||
return self.xml.get_object('activity_image')
|
||||
if type_ == PEPEventType.TUNE:
|
||||
return self.xml.get_object('tune_image')
|
||||
if type_ == PEPEventType.LOCATION:
|
||||
return self.xml.get_object('location_image')
|
||||
|
||||
@ensure_proper_control
|
||||
def _on_mood_received(self, _event):
|
||||
self._update_pep(PEPEventType.MOOD)
|
||||
|
||||
@ensure_proper_control
|
||||
def _on_activity_received(self, _event):
|
||||
self._update_pep(PEPEventType.ACTIVITY)
|
||||
|
||||
@ensure_proper_control
|
||||
def _on_tune_received(self, _event):
|
||||
self._update_pep(PEPEventType.TUNE)
|
||||
|
||||
@ensure_proper_control
|
||||
def _on_location_received(self, _event):
|
||||
self._update_pep(PEPEventType.LOCATION)
|
||||
|
||||
@ensure_proper_control
|
||||
def _on_nickname_received(self, _event):
|
||||
self.update_ui()
|
||||
self.parent_win.redraw_tab(self)
|
||||
self.parent_win.show_title()
|
||||
|
||||
@ensure_proper_control
|
||||
def _on_update_client_info(self, event):
|
||||
contact = app.contacts.get_contact(
|
||||
self.account, event.jid, event.resource)
|
||||
if contact is None:
|
||||
return
|
||||
self.xml.get_object('phone_image').set_visible(contact.uses_phone)
|
||||
|
||||
def _update_jingle(self, jingle_type):
|
||||
if jingle_type not in ('audio', 'video'):
|
||||
|
|
@ -446,16 +502,16 @@ class ChatControl(ChatControlBase):
|
|||
banner_image.show()
|
||||
if state == self.JINGLE_STATE_CONNECTING:
|
||||
banner_image.set_from_icon_name(
|
||||
Gtk.STOCK_CONVERT, Gtk.IconSize.MENU)
|
||||
'network-transmit-symbolic', Gtk.IconSize.MENU)
|
||||
elif state == self.JINGLE_STATE_CONNECTION_RECEIVED:
|
||||
banner_image.set_from_icon_name(
|
||||
"network-workgroup", Gtk.IconSize.MENU)
|
||||
'network-receive-symbolic', Gtk.IconSize.MENU)
|
||||
elif state == self.JINGLE_STATE_CONNECTED:
|
||||
banner_image.set_from_icon_name(
|
||||
Gtk.STOCK_CONNECT, Gtk.IconSize.MENU)
|
||||
'network-transmit-receive-symbolic', Gtk.IconSize.MENU)
|
||||
elif state == self.JINGLE_STATE_ERROR:
|
||||
banner_image.set_from_icon_name(
|
||||
"dialog-warning", Gtk.IconSize.MENU)
|
||||
'network-error-symbolic', Gtk.IconSize.MENU)
|
||||
self.update_toolbar()
|
||||
|
||||
def update_audio(self):
|
||||
|
|
@ -538,7 +594,7 @@ class ChatControl(ChatControlBase):
|
|||
self._set_jingle_state('video', state, sid=sid, reason=reason)
|
||||
|
||||
def _get_audio_content(self):
|
||||
session = app.connections[self.account].get_jingle_session(
|
||||
session = app.connections[self.account].get_module('Jingle').get_jingle_session(
|
||||
self.contact.get_full_jid(), self.audio_sid)
|
||||
return session.get_content('audio')
|
||||
|
||||
|
|
@ -548,12 +604,6 @@ class ChatControl(ChatControlBase):
|
|||
def on_num_button_released(self, released):
|
||||
self._get_audio_content()._stop_dtmf()
|
||||
|
||||
def on_dtmf_button_clicked(self, widget):
|
||||
self.dtmf_window.show_all()
|
||||
|
||||
def on_dtmf_window_focus_out_event(self, widget, event):
|
||||
self.dtmf_window.hide()
|
||||
|
||||
def on_mic_hscale_value_changed(self, widget, value):
|
||||
self._get_audio_content().set_mic_volume(value / 100)
|
||||
# Save volume to config
|
||||
|
|
@ -569,8 +619,6 @@ class ChatControl(ChatControlBase):
|
|||
If right-clicked, show popup
|
||||
"""
|
||||
if event.button == 3: # right click
|
||||
menu = Gtk.Menu()
|
||||
menuitem = Gtk.MenuItem.new_with_mnemonic(_('Save _As'))
|
||||
if self.TYPE_ID == message_control.TYPE_CHAT:
|
||||
sha = app.contacts.get_avatar_sha(
|
||||
self.account, self.contact.jid)
|
||||
|
|
@ -578,36 +626,27 @@ class ChatControl(ChatControlBase):
|
|||
else:
|
||||
sha = self.gc_contact.avatar_sha
|
||||
name = self.gc_contact.get_shown_name()
|
||||
id_ = menuitem.connect('activate',
|
||||
gtkgui_helpers.on_avatar_save_as_menuitem_activate, sha, name)
|
||||
self.handlers[id_] = menuitem
|
||||
menu.append(menuitem)
|
||||
menu.show_all()
|
||||
menu.connect('selection-done', lambda w: w.destroy())
|
||||
# show the menu
|
||||
menu.show_all()
|
||||
menu.attach_to_widget(widget, None)
|
||||
menu.popup(None, None, None, None, event.button, event.time)
|
||||
if sha is not None:
|
||||
gui_menu_builder.show_save_as_menu(sha, name)
|
||||
return True
|
||||
|
||||
def on_location_eventbox_button_release_event(self, widget, event):
|
||||
if 'geoloc' in self.contact.pep:
|
||||
location = self.contact.pep['geoloc']._pep_specific_data
|
||||
if ('lat' in location) and ('lon' in location):
|
||||
uri = 'https://www.openstreetmap.org/?' + \
|
||||
'mlat=%(lat)s&mlon=%(lon)s&zoom=16' % {'lat': location['lat'],
|
||||
'lon': location['lon']}
|
||||
helpers.launch_browser_mailer('url', uri)
|
||||
location = self.contact.pep['geoloc'].data
|
||||
if 'lat' in location and 'lon' in location:
|
||||
uri = geo_provider_from_location(location['lat'],
|
||||
location['lon'])
|
||||
open_uri(uri)
|
||||
|
||||
def on_location_eventbox_leave_notify_event(self, widget, event):
|
||||
"""
|
||||
Just moved the mouse so show the cursor
|
||||
"""
|
||||
cursor = gtkgui_helpers.get_cursor('LEFT_PTR')
|
||||
cursor = get_cursor('LEFT_PTR')
|
||||
self.parent_win.window.get_window().set_cursor(cursor)
|
||||
|
||||
def on_location_eventbox_enter_notify_event(self, widget, event):
|
||||
cursor = gtkgui_helpers.get_cursor('HAND2')
|
||||
cursor = get_cursor('HAND2')
|
||||
self.parent_win.window.get_window().set_cursor(cursor)
|
||||
|
||||
def update_ui(self):
|
||||
|
|
@ -616,16 +655,15 @@ class ChatControl(ChatControlBase):
|
|||
self.update_toolbar()
|
||||
|
||||
def _update_banner_state_image(self):
|
||||
contact = app.contacts.get_contact_with_highest_priority(self.account,
|
||||
self.contact.jid)
|
||||
contact = app.contacts.get_contact_with_highest_priority(
|
||||
self.account, self.contact.jid)
|
||||
if not contact or self.resource:
|
||||
# For transient contacts
|
||||
contact = self.contact
|
||||
show = contact.show
|
||||
jid = contact.jid
|
||||
|
||||
# Set banner image
|
||||
icon = gtkgui_helpers.get_iconset_name_for(show)
|
||||
icon = get_icon_name(show)
|
||||
banner_status_img = self.xml.get_object('banner_status_image')
|
||||
banner_status_img.set_from_icon_name(icon, Gtk.IconSize.DND)
|
||||
|
||||
|
|
@ -663,7 +701,8 @@ class ChatControl(ChatControlBase):
|
|||
if other_contact_.get_shown_name() == \
|
||||
self.contact.get_shown_name():
|
||||
acct_info = i18n.direction_mark + ' (%s)' % \
|
||||
GLib.markup_escape_text(self.account)
|
||||
GLib.markup_escape_text(
|
||||
app.get_account_label(self.account))
|
||||
break
|
||||
|
||||
status = contact.status
|
||||
|
|
@ -675,15 +714,14 @@ class ChatControl(ChatControlBase):
|
|||
status_reduced = ''
|
||||
status_escaped = GLib.markup_escape_text(status_reduced)
|
||||
|
||||
st = app.config.get('displayed_chat_state_notifications')
|
||||
cs = contact.chatstate
|
||||
if cs and st in ('composing_only', 'all'):
|
||||
if contact.show == 'offline':
|
||||
chatstate = ''
|
||||
elif st == 'all' or cs == 'composing':
|
||||
chatstate = helpers.get_uf_chatstate(cs)
|
||||
else:
|
||||
chatstate = ''
|
||||
if self.TYPE_ID == 'pm':
|
||||
cs = self.gc_contact.chatstate
|
||||
else:
|
||||
cs = app.contacts.get_combined_chatstate(
|
||||
self.account, self.contact.jid)
|
||||
|
||||
if app.config.get('show_chatstate_in_banner'):
|
||||
chatstate = helpers.get_uf_chatstate(cs)
|
||||
|
||||
label_text = '<span>%s</span><span size="x-small" weight="light">%s %s</span>' \
|
||||
% (name, acct_info, chatstate)
|
||||
|
|
@ -719,8 +757,8 @@ class ChatControl(ChatControlBase):
|
|||
return
|
||||
setattr(self, jingle_type + '_sid', None)
|
||||
setattr(self, jingle_type + '_state', self.JINGLE_STATE_NULL)
|
||||
session = app.connections[self.account].get_jingle_session(
|
||||
self.contact.get_full_jid(), sid)
|
||||
session = app.connections[self.account].get_module('Jingle').get_jingle_session(
|
||||
self.contact.get_full_jid(), sid)
|
||||
if session:
|
||||
content = session.get_content(jingle_type)
|
||||
if content:
|
||||
|
|
@ -730,10 +768,6 @@ class ChatControl(ChatControlBase):
|
|||
getattr(self, 'update_' + jingle_type)()
|
||||
|
||||
def on_jingle_button_toggled(self, state, jingle_type):
|
||||
img_name = 'gajim-%s_%s' % ({'audio': 'mic', 'video': 'cam'}[jingle_type],
|
||||
{True: 'active', False: 'inactive'}[state])
|
||||
path_to_img = gtkgui_helpers.get_icon_path(img_name)
|
||||
|
||||
if state:
|
||||
if getattr(self, jingle_type + '_state') == \
|
||||
self.JINGLE_STATE_NULL:
|
||||
|
|
@ -756,7 +790,7 @@ class ChatControl(ChatControlBase):
|
|||
in_da = self.xml.get_object('incoming_drawingarea')
|
||||
in_da.realize()
|
||||
in_xid = in_da.get_window().get_xid()
|
||||
sid = app.connections[self.account].start_video(
|
||||
sid = app.connections[self.account].get_module('Jingle').start_video(
|
||||
self.contact.get_full_jid(), in_xid, out_xid)
|
||||
else:
|
||||
sid = getattr(app.connections[self.account],
|
||||
|
|
@ -771,8 +805,6 @@ class ChatControl(ChatControlBase):
|
|||
self.close_jingle_content(jingle_type)
|
||||
|
||||
def set_lock_image(self):
|
||||
loggable = self.session and self.session.is_loggable()
|
||||
|
||||
encryption_state = {'visible': self.encryption is not None,
|
||||
'enc_type': self.encryption,
|
||||
'authenticated': False}
|
||||
|
|
@ -790,17 +822,17 @@ class ChatControl(ChatControlBase):
|
|||
if authenticated:
|
||||
authenticated_string = _('and authenticated')
|
||||
self.lock_image.set_from_icon_name(
|
||||
'security-high', Gtk.IconSize.MENU)
|
||||
'security-high-symbolic', Gtk.IconSize.MENU)
|
||||
else:
|
||||
authenticated_string = _('and NOT authenticated')
|
||||
self.lock_image.set_from_icon_name(
|
||||
'security-low', Gtk.IconSize.MENU)
|
||||
'security-low-symbolic', Gtk.IconSize.MENU)
|
||||
|
||||
tooltip = _('%(type)s encryption is active %(authenticated)s.') % {'type': enc_type, 'authenticated': authenticated_string}
|
||||
tooltip = _('%(type)s encryption is active %(authenticated)s') % \
|
||||
{'type': enc_type, 'authenticated': authenticated_string}
|
||||
|
||||
self.authentication_button.set_tooltip_text(tooltip)
|
||||
self.widget_set_visible(self.authentication_button, not visible)
|
||||
context = self.msg_scrolledwindow.get_style_context()
|
||||
self.lock_image.set_sensitive(visible)
|
||||
|
||||
def _on_authentication_button_clicked(self, widget):
|
||||
|
|
@ -823,8 +855,9 @@ class ChatControl(ChatControlBase):
|
|||
if obj.kind == KindConstant.CHAT_MSG_SENT:
|
||||
kind = 'outgoing'
|
||||
|
||||
self.print_conversation(obj.msgtxt, kind, tim=obj.timestamp,
|
||||
encrypted=obj.encrypted, correct_id=obj.correct_id,
|
||||
self.print_conversation(
|
||||
obj.msgtxt, kind, tim=obj.timestamp,
|
||||
encrypted=obj.encrypted, correct_id=obj.correct_id,
|
||||
msg_stanza_id=obj.message_id, additional_data=obj.additional_data)
|
||||
|
||||
def _nec_decrypted_message_received(self, obj):
|
||||
|
|
@ -886,8 +919,8 @@ class ChatControl(ChatControlBase):
|
|||
correct_id=obj.correct_id,
|
||||
additional_data=obj.additional_data)
|
||||
|
||||
def send_message(self, message, keyID='', chatstate=None, xhtml=None,
|
||||
process_commands=True, attention=False):
|
||||
def send_message(self, message, xhtml=None,
|
||||
process_commands=True, attention=False):
|
||||
"""
|
||||
Send a message to contact
|
||||
"""
|
||||
|
|
@ -903,24 +936,12 @@ class ChatControl(ChatControlBase):
|
|||
if message in ('', None, '\n'):
|
||||
return None
|
||||
|
||||
contact = self.contact
|
||||
keyID = contact.keyID
|
||||
|
||||
chatstates_on = app.config.get('outgoing_chat_state_notifications') != \
|
||||
'disabled'
|
||||
|
||||
chatstate_to_send = None
|
||||
if contact is not None:
|
||||
if contact.supports(NS_CHATSTATES):
|
||||
# send active chatstate on every message (as XEP says)
|
||||
chatstate_to_send = 'active'
|
||||
contact.our_chatstate = 'active'
|
||||
|
||||
self._schedule_activity_timers()
|
||||
|
||||
ChatControlBase.send_message(self, message, keyID, type_='chat',
|
||||
chatstate=chatstate_to_send, xhtml=xhtml,
|
||||
process_commands=process_commands, attention=attention)
|
||||
ChatControlBase.send_message(self,
|
||||
message,
|
||||
type_='chat',
|
||||
xhtml=xhtml,
|
||||
process_commands=process_commands,
|
||||
attention=attention)
|
||||
|
||||
def get_our_nick(self):
|
||||
return app.nicks[self.account]
|
||||
|
|
@ -944,7 +965,7 @@ class ChatControl(ChatControlBase):
|
|||
contact = self.contact
|
||||
|
||||
if additional_data is None:
|
||||
additional_data = {}
|
||||
additional_data = AdditionalDataDict()
|
||||
|
||||
if frm == 'status':
|
||||
if not app.config.get('print_status_in_chats'):
|
||||
|
|
@ -1031,29 +1052,27 @@ class ChatControl(ChatControlBase):
|
|||
['printed_' + self.type_id, self.type_id]))
|
||||
else:
|
||||
num_unread = 0
|
||||
# Set tab image (always 16x16); unread messages show the 'event' image
|
||||
tab_img = None
|
||||
|
||||
transport = None
|
||||
if app.jid_is_transport(jid):
|
||||
transport = app.get_transport_name_from_jid(jid)
|
||||
|
||||
if num_unread and app.config.get('show_unread_tab_icon'):
|
||||
img_16 = app.interface.roster.get_appropriate_state_images(
|
||||
self.contact.jid, icon_name='event')
|
||||
tab_img = img_16['event']
|
||||
icon_name = get_icon_name('event', transport=transport)
|
||||
else:
|
||||
contact = app.contacts.get_contact_with_highest_priority(
|
||||
self.account, self.contact.jid)
|
||||
if not contact or self.resource:
|
||||
# For transient contacts
|
||||
contact = self.contact
|
||||
img_16 = app.interface.roster.get_appropriate_state_images(
|
||||
self.contact.jid, icon_name=contact.show)
|
||||
tab_img = img_16[contact.show]
|
||||
icon_name = get_icon_name(contact.show, transport=transport)
|
||||
|
||||
return tab_img
|
||||
return icon_name
|
||||
|
||||
def prepare_context_menu(self, hide_buttonbar_items=False):
|
||||
"""
|
||||
Set compact view menuitem active state sets active and sensitivity state
|
||||
for history_menuitem (False for tranasports) and file_transfer_menuitem
|
||||
for history_menuitem (False for tranasports) and file_transfer_menuitem
|
||||
and hide()/show() for add_to_roster_menuitem
|
||||
"""
|
||||
if app.jid_is_transport(self.contact.jid):
|
||||
|
|
@ -1066,86 +1085,22 @@ class ChatControl(ChatControlBase):
|
|||
show_buttonbar_items=not hide_buttonbar_items)
|
||||
return menu
|
||||
|
||||
def send_chatstate(self, state, contact=None):
|
||||
"""
|
||||
Send OUR chatstate as STANDLONE chat state message (eg. no body)
|
||||
to contact only if new chatstate is different from the previous one
|
||||
if jid is not specified, send to active tab
|
||||
"""
|
||||
# JEP 85 does not allow resending the same chatstate
|
||||
# this function checks for that and just returns so it's safe to call it
|
||||
# with same state.
|
||||
|
||||
# This functions also checks for violation in state transitions
|
||||
# and raises RuntimeException with appropriate message
|
||||
# more on that http://xmpp.org/extensions/xep-0085.html#statechart
|
||||
|
||||
# do not send if we have chat state notifications disabled
|
||||
# that means we won't reply to the <active/> from other peer
|
||||
# so we do not broadcast jep85 capabalities
|
||||
chatstate_setting = app.config.get('outgoing_chat_state_notifications')
|
||||
if chatstate_setting == 'disabled':
|
||||
return
|
||||
|
||||
# Dont leak presence to contacts
|
||||
# which are not allowed to see our status
|
||||
if contact and contact.sub in ('to', 'none'):
|
||||
return
|
||||
|
||||
if self.contact.jid == app.get_jid_from_account(self.account):
|
||||
return
|
||||
|
||||
elif chatstate_setting == 'composing_only' and state != 'active' and\
|
||||
state != 'composing':
|
||||
return
|
||||
|
||||
if contact is None:
|
||||
contact = self.parent_win.get_active_contact()
|
||||
if contact is None:
|
||||
# contact was from pm in MUC, and left the room so contact is None
|
||||
# so we cannot send chatstate anymore
|
||||
return
|
||||
|
||||
# Don't send chatstates to offline contacts
|
||||
if contact.show == 'offline':
|
||||
return
|
||||
|
||||
if not contact.supports(NS_CHATSTATES):
|
||||
return
|
||||
if contact.our_chatstate == False:
|
||||
return
|
||||
|
||||
# if the new state we wanna send (state) equals
|
||||
# the current state (contact.our_chatstate) then return
|
||||
if contact.our_chatstate == state:
|
||||
return
|
||||
|
||||
# if wel're inactive prevent composing (XEP violation)
|
||||
if contact.our_chatstate == 'inactive' and state == 'composing':
|
||||
# go active before
|
||||
app.log('chatstates').info('%-10s - %s', 'active', self.contact.jid)
|
||||
app.nec.push_outgoing_event(MessageOutgoingEvent(None,
|
||||
account=self.account, jid=self.contact.jid, chatstate='active',
|
||||
control=self))
|
||||
contact.our_chatstate = 'active'
|
||||
self.reset_kbd_mouse_timeout_vars()
|
||||
|
||||
app.log('chatstates').info('%-10s - %s', state, self.contact.jid)
|
||||
app.nec.push_outgoing_event(MessageOutgoingEvent(None,
|
||||
account=self.account, jid=self.contact.jid, chatstate=state,
|
||||
control=self))
|
||||
|
||||
contact.our_chatstate = state
|
||||
if state == 'active':
|
||||
self.reset_kbd_mouse_timeout_vars()
|
||||
|
||||
def shutdown(self):
|
||||
# PluginSystem: removing GUI extension points connected with ChatControl
|
||||
# instance object
|
||||
app.plugin_manager.remove_gui_extension_point('chat_control', self)
|
||||
|
||||
app.ged.remove_event_handler('pep-received', ged.GUI1,
|
||||
self._nec_pep_received)
|
||||
app.ged.remove_event_handler('nickname-received', ged.GUI1,
|
||||
self._on_nickname_received)
|
||||
app.ged.remove_event_handler('mood-received', ged.GUI1,
|
||||
self._on_mood_received)
|
||||
app.ged.remove_event_handler('activity-received', ged.GUI1,
|
||||
self._on_activity_received)
|
||||
app.ged.remove_event_handler('tune-received', ged.GUI1,
|
||||
self._on_tune_received)
|
||||
app.ged.remove_event_handler('location-received', ged.GUI1,
|
||||
self._on_location_received)
|
||||
app.ged.remove_event_handler('update-client-info', ged.GUI1,
|
||||
self._on_update_client_info)
|
||||
if self.TYPE_ID == message_control.TYPE_CHAT:
|
||||
app.ged.remove_event_handler('update-roster-avatar', ged.GUI1,
|
||||
self._nec_update_avatar)
|
||||
|
|
@ -1168,9 +1123,8 @@ class ChatControl(ChatControlBase):
|
|||
self.unsubscribe_events()
|
||||
|
||||
# Send 'gone' chatstate
|
||||
self.send_chatstate('gone', self.contact)
|
||||
self.contact.chatstate = None
|
||||
self.contact.our_chatstate = None
|
||||
con = app.connections[self.account]
|
||||
con.get_module('Chatstate').set_chatstate(self.contact, Chatstate.GONE)
|
||||
|
||||
for jingle_type in ('audio', 'video'):
|
||||
self.close_jingle_content(jingle_type)
|
||||
|
|
@ -1210,35 +1164,45 @@ class ChatControl(ChatControlBase):
|
|||
def safe_shutdown(self):
|
||||
return False
|
||||
|
||||
def allow_shutdown(self, method, on_yes, on_no, on_minimize):
|
||||
if time.time() - app.last_message_time[self.account]\
|
||||
[self.get_full_jid()] < 2:
|
||||
def allow_shutdown(self, method, on_yes, on_no, _on_minimize):
|
||||
time_ = app.last_message_time[self.account][self.get_full_jid()]
|
||||
if time.time() - time_ < 2:
|
||||
# 2 seconds
|
||||
|
||||
def on_ok():
|
||||
on_yes(self)
|
||||
|
||||
def on_cancel():
|
||||
on_no(self)
|
||||
|
||||
ConfirmationDialog(
|
||||
#%s is being replaced in the code with JID
|
||||
_('You just received a new message from "%s"') % \
|
||||
self.contact.jid,
|
||||
_('If you close this tab and you have history disabled, '\
|
||||
'this message will be lost.'), on_response_ok=on_ok,
|
||||
on_response_cancel=on_cancel,
|
||||
transient_for=self.parent_win.window)
|
||||
NewConfirmationDialog(
|
||||
_('Close'),
|
||||
_('You just received a new message from %s') % self.contact.jid,
|
||||
_('If you close this tab and you have history disabled, '
|
||||
'this message will be lost.'),
|
||||
[DialogButton.make('Cancel',
|
||||
callback=lambda: on_no(self)),
|
||||
DialogButton.make('OK',
|
||||
text=_('Close'),
|
||||
callback=lambda: on_yes(self))],
|
||||
transient_for=self.parent_win.window).show()
|
||||
return
|
||||
on_yes(self)
|
||||
|
||||
def _nec_chatstate_received(self, obj):
|
||||
"""
|
||||
Handle incoming chatstate that jid SENT TO us
|
||||
"""
|
||||
def _nec_chatstate_received(self, event):
|
||||
if event.account != self.account:
|
||||
return
|
||||
|
||||
if self.TYPE_ID == 'pm':
|
||||
if event.contact != self< | ||||