Compare commits
52 Commits
v1.1.2-plu
...
v1.1.3-plu
Author | SHA1 | Date |
---|---|---|
'leftie | c32f46e99f | |
Yann Leboulanger | 257f784804 | |
Yann Leboulanger | b6b56a282e | |
Philipp Hörist | 9787729423 | |
Sebastiaan Lokhorst | da04f7fe39 | |
Alexander Bogdanov | ff9afe194e | |
Philipp Hörist | 9e0fb54173 | |
Philipp Hörist | a7466406a5 | |
Philipp Hörist | 27e2786bcc | |
Marcin Mielniczuk | db942378a1 | |
Philipp Hörist | bf804f18d4 | |
Philipp Hörist | 5f562fb0ab | |
Philipp Hörist | 72b8c0ab7f | |
Philipp Hörist | 80f37bb3fa | |
Philipp Hörist | cb4e60c481 | |
Daniel Brötzmann | e01e552f65 | |
Philipp Hörist | 366b3df235 | |
Philipp Hörist | f9872d3abb | |
Philipp Hörist | da5ffe4bcc | |
Philipp Hörist | 669671e0b2 | |
Philipp Hörist | 0e9422ae9b | |
Philipp Hörist | 2f5d00d1f3 | |
Philipp Hörist | 23c08892a0 | |
Daniel Brötzmann | 838b04d6b6 | |
Daniel Brötzmann | 64bda9cf79 | |
Dominion | 40cf9488b7 | |
André Apitzsch | 65dc8ed892 | |
André Apitzsch | 4099b01f9d | |
Philipp Hörist | c972196a4f | |
Philipp Hörist | 06ca9b39e1 | |
Philipp Hörist | bb4daa2945 | |
Philipp Hörist | d159a7a1e2 | |
Philipp Hörist | 7ba9bc4b04 | |
Philipp Hörist | 76cb4d1d08 | |
Philipp Hörist | 3729b8f94f | |
Philipp Hörist | 36e80622bc | |
Philipp Hörist | c1948d05e0 | |
Philipp Hörist | c0178db779 | |
Philipp Hörist | c5d2f8bdab | |
Philipp Hörist | 56e40954b7 | |
André Apitzsch | c4b5671b69 | |
André Apitzsch | 0dfce19d22 | |
Philipp Hörist | f4b4e9cc88 | |
Philipp Hörist | 064f249c5a | |
Philipp Hörist | 4189d5b9c8 | |
Philipp Hörist | a1d68677d0 | |
Philipp Hörist | 05f1c78098 | |
Philipp Hörist | 9419e8ddf7 | |
Philipp Hörist | bf9aed69fe | |
Philipp Hörist | 14b4488b07 | |
Philipp Hörist | 14fb085766 | |
Yann Leboulanger | 8c0776e53e |
26
ChangeLog
26
ChangeLog
|
@ -1,3 +1,29 @@
|
|||
Gajim 1.1.3 (24 April 2019)
|
||||
|
||||
New
|
||||
|
||||
* Add a mobile phone indicator to the chat window
|
||||
* Rework HTTPUpload dialog
|
||||
* Add a "paste as quote" option to the message input
|
||||
|
||||
Bug fixes
|
||||
|
||||
* #8822 Fix memory leak when using spell checker
|
||||
* #9514 Fix jingle filetransfers not working in some circumstances
|
||||
* #9573 Dont leak DNS query when connecting over proxy
|
||||
* #9578 Determine Windows version more reliably
|
||||
* #9622 Fix an error while quitting Gajim
|
||||
* #9633 Fix an error while sending a file
|
||||
* #9637 Restore window size correctly on wayland
|
||||
* #9660 GPG Agent setting is ignored
|
||||
* #9645 Make zeroconf IPV6 compatible
|
||||
* Improve dark theme colors
|
||||
* Fix access to GnuPG keys
|
||||
* Use UUID4 item ids for pubsub posts
|
||||
* Dont send invalid show values
|
||||
* Windows: Dont override format region settings
|
||||
* Various smaller improvements
|
||||
|
||||
Gajim 1.1.2 (15 January 2019)
|
||||
|
||||
Bug fixes
|
||||
|
|
|
@ -32,8 +32,8 @@ build_script:
|
|||
|
||||
bash "git clone C:/projects/gajim C:/msys64/home/appveyor/gajim"
|
||||
bash "C:/msys64/home/appveyor/gajim/win/build.sh $($env:MSYS_ARCH)"
|
||||
Push-AppveyorArtifact "$($env:BUILDROOT)/Gajim.exe" -FileName "Gajim-1.1.1-$($env:ARCH)-$($env:TIME_STRING).exe"
|
||||
Push-AppveyorArtifact "$($env:BUILDROOT)/Gajim-Portable.exe" -FileName "Gajim-Portable-1.1.1-$($env:ARCH)-$($env:TIME_STRING).exe"
|
||||
Push-AppveyorArtifact "$($env:BUILDROOT)/Gajim.exe" -FileName "Gajim-1.1.2-$($env:ARCH)-$($env:TIME_STRING).exe"
|
||||
Push-AppveyorArtifact "$($env:BUILDROOT)/Gajim-Portable.exe" -FileName "Gajim-Portable-1.1.2-$($env:ARCH)-$($env:TIME_STRING).exe"
|
||||
|
||||
# on_finish:
|
||||
# - ps: $blockRdp = $true; iex ((new-object net.webclient).DownloadString('https://raw.githubusercontent.com/appveyor/ci/master/scripts/enable-rdp.ps1'))
|
||||
|
|
|
@ -101,6 +101,7 @@
|
|||
<content_attribute id="money-gambling">none</content_attribute>
|
||||
</content_rating>
|
||||
<releases>
|
||||
<release version="1.1.3" date="2019-04-23" />
|
||||
<release version="1.1.2" date="2019-01-15" />
|
||||
<release version="1.1.1" date="2018-12-23" />
|
||||
<release version="1.1.0" date="2018-11-06" />
|
||||
|
|
|
@ -3,8 +3,6 @@ runtime: org.gnome.Platform
|
|||
runtime-version: 3.30
|
||||
sdk: org.gnome.Sdk
|
||||
command: gajim
|
||||
tags: nightly
|
||||
desktop-file-name-prefix: '(Nightly) '
|
||||
finish-args:
|
||||
- --share=ipc
|
||||
- --share=network
|
||||
|
@ -22,6 +20,8 @@ finish-args:
|
|||
- --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
|
||||
|
||||
|
@ -62,8 +62,8 @@ modules:
|
|||
- pip3 install --prefix=/app .
|
||||
sources:
|
||||
- type: archive
|
||||
url: https://files.pythonhosted.org/packages/e7/a7/4cd50e57cc6f436f1cc3a7e8fa700ff9b8b4d471620629074913e3735fb2/cffi-1.11.5.tar.gz
|
||||
sha256: e90f17980e6ab0f3c2f3730e56d1fe9bcba1891eeea58966e89d352492cc74f4
|
||||
url: https://files.pythonhosted.org/packages/64/7c/27367b38e6cc3e1f49f193deb761fe75cda9f95da37b67b422e62281fcac/cffi-1.12.2.tar.gz
|
||||
sha256: e113878a446c6228669144ae8a56e268c91b7f1fafae927adc4879d9849e0ea7
|
||||
|
||||
- name: python3-asn1crypto
|
||||
buildsystem: simple
|
||||
|
@ -77,11 +77,11 @@ modules:
|
|||
- name: python3-idna
|
||||
buildsystem: simple
|
||||
build-commands:
|
||||
- pip3 install --prefix=/app idna-2.7-py2.py3-none-any.whl
|
||||
- pip3 install --prefix=/app idna-2.8-py2.py3-none-any.whl
|
||||
sources:
|
||||
- type: file
|
||||
url: https://files.pythonhosted.org/packages/4b/2a/0276479a4b3caeb8a8c1af2f8e4355746a97fab05a372e4a2c6a6b876165/idna-2.7-py2.py3-none-any.whl
|
||||
sha256: 156a6814fb5ac1fc6850fb002e0852d56c0c8d2531923a51032d1b70760e186e
|
||||
url: https://files.pythonhosted.org/packages/14/2c/cd551d81dbe15200be1cf41cd03869a46fe7226e7450af7a6545bfc474c9/idna-2.8-py2.py3-none-any.whl
|
||||
sha256: ea8b7f6188e6fa117537c3df7da9fc686d485087abf6ac197f9c46432f7e4a3c
|
||||
|
||||
- name: python3-cryptography
|
||||
buildsystem: simple
|
||||
|
@ -89,17 +89,17 @@ modules:
|
|||
- pip3 install --prefix=/app .
|
||||
sources:
|
||||
- type: archive
|
||||
url: https://files.pythonhosted.org/packages/22/21/233e38f74188db94e8451ef6385754a98f3cad9b59bedf3a8e8b14988be4/cryptography-2.3.1.tar.gz
|
||||
sha256: 8d10113ca826a4c29d5b85b2c4e045ffa8bad74fb525ee0eceb1d38d4c70dfd6
|
||||
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-18.0.0-py2.py3-none-any.whl
|
||||
- pip3 install --prefix=/app pyOpenSSL-19.0.0-py2.py3-none-any.whl
|
||||
sources:
|
||||
- type: file
|
||||
url: https://files.pythonhosted.org/packages/96/af/9d29e6bd40823061aea2e0574ccb2fcf72bfd6130ce53d32773ec375458c/pyOpenSSL-18.0.0-py2.py3-none-any.whl
|
||||
sha256: 26ff56a6b5ecaf3a2a59f132681e2a80afcc76b4f902f612f518f92c2a1bf854
|
||||
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:
|
||||
|
@ -122,31 +122,31 @@ modules:
|
|||
- name: python3-secretstorage
|
||||
buildsystem: simple
|
||||
build-commands:
|
||||
- pip3 install --prefix=/app SecretStorage-3.1.0-py3-none-any.whl
|
||||
- pip3 install --prefix=/app SecretStorage-3.1.1-py3-none-any.whl
|
||||
sources:
|
||||
- type: file
|
||||
url: https://files.pythonhosted.org/packages/d8/e8/80975fd281764c80b2eb581a7f25d2109786e273b8925e8161bd2d06d10a/SecretStorage-3.1.0-py3-none-any.whl
|
||||
sha256: 20196abd1a9d1310df7573d58ca6e7ed9292218c98ca3638eea07beb16080343
|
||||
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.2.3-py2.py3-none-any.whl
|
||||
- pip3 install --prefix=/app entrypoints-0.3-py2.py3-none-any.whl
|
||||
sources:
|
||||
- type: file
|
||||
url: https://files.pythonhosted.org/packages/cc/8b/4eefa9b47f1910b3d2081da67726b066e379b04ca897acfe9f92bac56147/entrypoints-0.2.3-py2.py3-none-any.whl
|
||||
sha256: 10ad569bb245e7e2ba425285b9fa3e8178a0dc92fc53b1e1c553805e15a8825b
|
||||
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-16.0.2-py2.py3-none-any.whl
|
||||
- pip3 install --prefix=/app keyring-18.0.0-py2.py3-none-any.whl
|
||||
cleanup:
|
||||
- /bin
|
||||
sources:
|
||||
- type: file
|
||||
url: https://files.pythonhosted.org/packages/5f/cb/dc7b2215cd82b77e7b8b48abd8989c1b09990d4c91a3ccfdc18a61157b36/keyring-16.0.2-py2.py3-none-any.whl
|
||||
sha256: 2a5cf5e596cbf8b66b98b8df2c214adfe21e6e18baa82006b2c482bd0c4be94c
|
||||
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
|
||||
|
@ -182,8 +182,8 @@ modules:
|
|||
- pip3 install --prefix=/app .
|
||||
sources:
|
||||
- type: archive
|
||||
url: https://files.pythonhosted.org/packages/24/54/23a475a0d7d3664ea21b14ce907245dc390496f31d229a9aac2ae20c7c28/nbxmpp-0.6.8.tar.gz
|
||||
sha256: 8c2b4b8aac1a8c6d07c1e30af542fde20a70a9b8c7c04017e9cea0db654437c6
|
||||
url: https://files.pythonhosted.org/packages/d6/01/34b2a441926780f26edd21490158afe0eb76beae4efbb6bc4d3323eae69a/nbxmpp-0.6.10.tar.gz
|
||||
sha256: cd73417777e4847fdd8d0d96c7cafc606952edbd2b9d52a2a72bb2aaa04d08ef
|
||||
|
||||
- name: gajim
|
||||
buildsystem: simple
|
||||
|
@ -193,5 +193,6 @@ modules:
|
|||
sources:
|
||||
- type: git
|
||||
url: https://dev.gajim.org/gajim/gajim.git
|
||||
branch: gajim_1.1
|
||||
post-install:
|
||||
- install -d /app/plugins
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import os
|
||||
import subprocess
|
||||
|
||||
__version__ = "1.1.2"
|
||||
__version__ = "1.1.3"
|
||||
|
||||
IS_FLATPAK = False
|
||||
if os.path.exists('/app/share/run-as-flatpak'):
|
||||
|
|
|
@ -379,6 +379,7 @@ class GajimApplication(Gtk.Application):
|
|||
act = Gio.SimpleAction.new_stateful(
|
||||
'agent', None,
|
||||
GLib.Variant.new_boolean(app.config.get('use_gpg_agent')))
|
||||
act.connect('change-state', app_actions.on_use_pgp_agent)
|
||||
self.add_action(act)
|
||||
|
||||
# General Actions
|
||||
|
|
|
@ -225,6 +225,9 @@ class ChatControl(ChatControlBase):
|
|||
|
||||
app.ged.register_event_handler('pep-received', ged.GUI1,
|
||||
self._nec_pep_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,
|
||||
|
@ -435,6 +438,17 @@ class ChatControl(ChatControlBase):
|
|||
else:
|
||||
self.update_pep(obj.pep_type)
|
||||
|
||||
def _on_update_client_info(self, event):
|
||||
if event.account != self.account:
|
||||
return
|
||||
if event.jid != self.contact.jid:
|
||||
return
|
||||
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'):
|
||||
return
|
||||
|
@ -1063,6 +1077,9 @@ class ChatControl(ChatControlBase):
|
|||
|
||||
app.ged.remove_event_handler('pep-received', ged.GUI1,
|
||||
self._nec_pep_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)
|
||||
|
|
|
@ -323,7 +323,6 @@ class ChatControlBase(MessageControl, ChatCommandProcessor, CommandTools):
|
|||
self.set_emoticon_popover()
|
||||
|
||||
# Attach speller
|
||||
self.spell_checker = None
|
||||
self.set_speller()
|
||||
self.conv_textview.tv.show()
|
||||
|
||||
|
@ -474,15 +473,15 @@ class ChatControlBase(MessageControl, ChatCommandProcessor, CommandTools):
|
|||
if gspell_lang is None:
|
||||
return
|
||||
|
||||
self.spell_checker = Gspell.Checker.new(gspell_lang)
|
||||
spell_checker = Gspell.Checker.new(gspell_lang)
|
||||
spell_buffer = Gspell.TextBuffer.get_from_gtk_text_buffer(
|
||||
self.msg_textview.get_buffer())
|
||||
spell_buffer.set_spell_checker(self.spell_checker)
|
||||
spell_buffer.set_spell_checker(spell_checker)
|
||||
spell_view = Gspell.TextView.get_from_gtk_text_view(self.msg_textview)
|
||||
spell_view.set_inline_spell_checking(False)
|
||||
spell_view.set_enable_language_menu(True)
|
||||
|
||||
self.spell_checker.connect('notify::language', self.on_language_changed)
|
||||
spell_checker.connect('notify::language', self.on_language_changed)
|
||||
|
||||
def get_speller_language(self):
|
||||
per_type = 'contacts'
|
||||
|
@ -560,14 +559,27 @@ class ChatControlBase(MessageControl, ChatCommandProcessor, CommandTools):
|
|||
id_ = item.connect('activate', self.msg_textview.clear)
|
||||
self.handlers[id_] = item
|
||||
|
||||
paste_item = Gtk.MenuItem.new_with_label(_('Paste as quote'))
|
||||
id_ = paste_item.connect('activate', self.paste_clipboard_as_quote)
|
||||
self.handlers[id_] = paste_item
|
||||
menu.append(paste_item)
|
||||
|
||||
menu.show_all()
|
||||
|
||||
def on_quote(self, widget, text):
|
||||
def insert_as_quote(self, text: str) -> None:
|
||||
self.msg_textview.remove_placeholder()
|
||||
text = '>' + text.replace('\n', '\n>') + '\n'
|
||||
text = '> ' + text.replace('\n', '\n> ') + '\n'
|
||||
message_buffer = self.msg_textview.get_buffer()
|
||||
message_buffer.insert_at_cursor(text)
|
||||
|
||||
def paste_clipboard_as_quote(self, _item: Gtk.MenuItem) -> None:
|
||||
clipboard = Gtk.Clipboard.get(Gdk.SELECTION_CLIPBOARD)
|
||||
text = clipboard.wait_for_text()
|
||||
self.insert_as_quote(text)
|
||||
|
||||
def on_quote(self, widget, text):
|
||||
self.insert_as_quote(text)
|
||||
|
||||
# moved from ChatControl
|
||||
def _on_banner_eventbox_button_press_event(self, widget, event):
|
||||
"""
|
||||
|
|
|
@ -74,7 +74,7 @@ class StandardCommonCommands(CommandContainer):
|
|||
def say(self, message):
|
||||
self.send(message)
|
||||
|
||||
@command(raw=True)
|
||||
@command('we','us',raw=True)
|
||||
@doc(_("Send action (in the third person) to the current chat"))
|
||||
def me(self, action):
|
||||
self.send("/me %s" % action)
|
||||
|
|
|
@ -39,11 +39,13 @@ from distutils.version import LooseVersion as V
|
|||
from collections import namedtuple
|
||||
|
||||
import nbxmpp
|
||||
from gi.repository import Gdk
|
||||
|
||||
import gajim
|
||||
from gajim.common import config as c_config
|
||||
from gajim.common import configpaths
|
||||
from gajim.common import ged as ged_module
|
||||
from gajim.common.const import Display
|
||||
from gajim.common.contacts import LegacyContactsAPI
|
||||
from gajim.common.events import Events
|
||||
from gajim.common.types import NetworkEventsControllerT # pylint: disable=unused-import
|
||||
|
@ -148,6 +150,7 @@ socks5queue = None
|
|||
gupnp_igd = None
|
||||
|
||||
gajim_identity = {'type': 'pc', 'category': 'client', 'name': 'Gajim'}
|
||||
|
||||
gajim_common_features = [nbxmpp.NS_BYTESTREAM, nbxmpp.NS_SI, nbxmpp.NS_FILE,
|
||||
nbxmpp.NS_MUC, nbxmpp.NS_MUC_USER, nbxmpp.NS_MUC_ADMIN, nbxmpp.NS_MUC_OWNER,
|
||||
nbxmpp.NS_MUC_CONFIG, nbxmpp.NS_COMMANDS, nbxmpp.NS_DISCO_INFO, 'ipv6',
|
||||
|
@ -192,6 +195,18 @@ def is_installed(dependency):
|
|||
def is_flatpak():
|
||||
return gajim.IS_FLATPAK
|
||||
|
||||
def is_display(display):
|
||||
# XWayland reports as Display X11, so try with env var
|
||||
is_wayland = os.environ.get('XDG_SESSION_TYPE') == 'wayland'
|
||||
if is_wayland and display == Display.WAYLAND:
|
||||
return True
|
||||
|
||||
default = Gdk.Display.get_default()
|
||||
if default is None:
|
||||
log('gajim').warning('Could not determine window manager')
|
||||
return False
|
||||
return default.__class__.__name__ == display.value
|
||||
|
||||
def disable_dependency(dependency):
|
||||
_dependencies[dependency] = False
|
||||
|
||||
|
|
|
@ -77,6 +77,14 @@ def client_supports(client_caps, requested_feature):
|
|||
return requested_feature not in FEATURE_BLACKLIST
|
||||
return False
|
||||
|
||||
def get_client_identity(client_caps):
|
||||
lookup_item = client_caps.get_cache_lookup_strategy()
|
||||
cache_item = lookup_item(capscache)
|
||||
|
||||
for identity in cache_item.identities:
|
||||
if identity.get('category') == 'client':
|
||||
return identity.get('type')
|
||||
|
||||
def create_suitable_client_caps(node, caps_hash, hash_method, fjid=None):
|
||||
"""
|
||||
Create and return a suitable ClientCaps object for the given node,
|
||||
|
|
|
@ -905,10 +905,6 @@ class Connection(CommonConnection, ConnectionHandlers):
|
|||
]
|
||||
self._hostname = hostname
|
||||
|
||||
if h:
|
||||
app.resolver.resolve('_xmppconnect.' + helpers.idn_to_ascii(h),
|
||||
self._on_resolve_txt, type_='txt')
|
||||
|
||||
if use_srv and self._proxy is None:
|
||||
self._srv_hosts = []
|
||||
|
||||
|
@ -918,6 +914,9 @@ class Connection(CommonConnection, ConnectionHandlers):
|
|||
for service in services:
|
||||
record_name = '_' + service + '._tcp.' + helpers.idn_to_ascii(h)
|
||||
app.resolver.resolve(record_name, self._on_resolve_srv)
|
||||
|
||||
app.resolver.resolve('_xmppconnect.' + helpers.idn_to_ascii(h),
|
||||
self._on_resolve_txt, type_='txt')
|
||||
else:
|
||||
self._connect_to_next_host()
|
||||
|
||||
|
|
|
@ -193,6 +193,13 @@ class SyncThreshold(IntEnum):
|
|||
return str(self.value)
|
||||
|
||||
|
||||
class Display(Enum):
|
||||
X11 = 'X11Display'
|
||||
WAYLAND = 'GdkWaylandDisplay'
|
||||
WIN32 = 'GdkWin32Display'
|
||||
QUARTZ = 'GdkQuartzDisplay'
|
||||
|
||||
|
||||
ACTIVITIES = {
|
||||
'doing_chores': {
|
||||
'category': _('Doing Chores'),
|
||||
|
|
|
@ -116,6 +116,10 @@ class CommonContact(XMPPEntity):
|
|||
return False
|
||||
return caps_cache.client_supports(self.client_caps, requested_feature)
|
||||
|
||||
@property
|
||||
def uses_phone(self):
|
||||
return caps_cache.get_client_identity(self.client_caps) == 'phone'
|
||||
|
||||
|
||||
class Contact(CommonContact):
|
||||
"""
|
||||
|
|
|
@ -42,6 +42,7 @@ import logging
|
|||
import json
|
||||
import shutil
|
||||
import collections
|
||||
from io import StringIO
|
||||
from datetime import datetime, timedelta
|
||||
from distutils.version import LooseVersion as V
|
||||
from encodings.punycode import punycode_encode
|
||||
|
@ -51,11 +52,17 @@ import nbxmpp
|
|||
from nbxmpp.stringprepare import nameprep
|
||||
import precis_i18n.codec # pylint: disable=unused-import
|
||||
|
||||
from gajim.common import app
|
||||
from gajim.common import caps_cache
|
||||
from gajim.common import configpaths
|
||||
from gajim.common.i18n import Q_
|
||||
from gajim.common.i18n import _
|
||||
from gajim.common.i18n import ngettext
|
||||
from gajim.common.const import Display
|
||||
|
||||
if app.is_installed('PYCURL'):
|
||||
import pycurl
|
||||
|
||||
|
||||
log = logging.getLogger('gajim.c.helpers')
|
||||
|
||||
|
@ -552,12 +559,6 @@ def datetime_tuple(timestamp):
|
|||
tim = tim.timetuple()
|
||||
return tim
|
||||
|
||||
|
||||
from gajim.common import app
|
||||
if app.is_installed('PYCURL'):
|
||||
import pycurl
|
||||
from io import StringIO
|
||||
|
||||
def convert_bytes(string):
|
||||
suffix = ''
|
||||
# IEC standard says KiB = 1024 bytes KB = 1000 bytes
|
||||
|
@ -1534,3 +1535,14 @@ class AdditionalDataDict(collections.UserDict):
|
|||
del _dict[key]
|
||||
except KeyError:
|
||||
return
|
||||
|
||||
|
||||
def save_roster_position(window):
|
||||
if not app.config.get('save-roster-position'):
|
||||
return
|
||||
if app.is_display(Display.WAYLAND):
|
||||
return
|
||||
x_pos, y_pos = window.get_position()
|
||||
log.debug('Save roster position: %s %s', x_pos, y_pos)
|
||||
app.config.set('roster_x-position', x_pos)
|
||||
app.config.set('roster_y-position', y_pos)
|
||||
|
|
|
@ -151,13 +151,18 @@ def ngettext(s_sing, s_plural, n, replace_sing=None, replace_plural=None):
|
|||
try:
|
||||
locale.setlocale(locale.LC_ALL, '')
|
||||
except locale.Error as error:
|
||||
print(error)
|
||||
print(error, file=sys.stderr)
|
||||
|
||||
try:
|
||||
LANG = get_default_lang()
|
||||
print('Found default language: %s' % LANG)
|
||||
if os.name == 'nt':
|
||||
# Set the env var on Windows because gettext.find() uses it to
|
||||
# find the translation
|
||||
# Use LANGUAGE instead of LANG, LANG sets LC_ALL and thus
|
||||
# doesn't retain other region settings like LC_TIME
|
||||
os.environ['LANGUAGE'] = LANG
|
||||
except Exception as error:
|
||||
print('Failed to determine default language')
|
||||
print('Failed to determine default language', file=sys.stderr)
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
|
||||
|
@ -173,6 +178,6 @@ for dir_ in iter_locale_dirs():
|
|||
else:
|
||||
break
|
||||
else:
|
||||
print('No translations found')
|
||||
print('Dirs searched: %s' % get_locale_dirs())
|
||||
print('No translations found', file=sys.stderr)
|
||||
print('Dirs searched: %s' % get_locale_dirs(), file=sys.stderr)
|
||||
_ = _translation.gettext
|
||||
|
|
|
@ -445,6 +445,8 @@ class JingleSession:
|
|||
if child.getName() == 'checksum':
|
||||
hash_ = child.getTag('file').getTag(name='hash',
|
||||
namespace=nbxmpp.NS_HASHES_2)
|
||||
if hash_ is None:
|
||||
continue
|
||||
algo = hash_.getAttr('algo')
|
||||
if algo in nbxmpp.Hashes2.supported:
|
||||
file_props = FilesProp.getFileProp(self.connection.name,
|
||||
|
|
|
@ -100,7 +100,7 @@ def get_context(fingerprint, verify_cb=None, remote_jid=None):
|
|||
flags = (SSL.OP_NO_SSLv2 | SSL.OP_NO_SSLv3 | SSL.OP_SINGLE_DH_USE \
|
||||
| SSL.OP_NO_TICKET)
|
||||
ctx.set_options(flags)
|
||||
ctx.set_cipher_list('HIGH:!aNULL:!3DES')
|
||||
ctx.set_cipher_list(b'HIGH:!aNULL:!3DES')
|
||||
|
||||
if fingerprint == 'server': # for testing purposes only
|
||||
ctx.set_verify(SSL.VERIFY_NONE|SSL.VERIFY_FAIL_IF_NO_PEER_CERT,
|
||||
|
|
|
@ -28,7 +28,7 @@ def parseLogLevel(arg):
|
|||
return int(arg)
|
||||
if arg.isupper() and hasattr(logging, arg):
|
||||
return getattr(logging, arg)
|
||||
print(_('%s is not a valid loglevel') % repr(arg))
|
||||
print(_('%s is not a valid loglevel') % repr(arg), file=sys.stderr)
|
||||
return 0
|
||||
|
||||
def parseLogTarget(arg):
|
||||
|
@ -69,7 +69,8 @@ def parseAndSetLogLevels(arg):
|
|||
target = parseLogTarget(target.strip())
|
||||
if target:
|
||||
logging.getLogger(target).setLevel(level)
|
||||
print("Logger %s level set to %d" % (target, level))
|
||||
print("Logger %s level set to %d" % (target, level),
|
||||
file=sys.stderr)
|
||||
|
||||
|
||||
class colors:
|
||||
|
|
|
@ -207,6 +207,11 @@ class Message:
|
|||
'gc_control': gc_control
|
||||
}
|
||||
|
||||
app.nec.push_incoming_event(NetworkEvent('update-client-info',
|
||||
account=self._account,
|
||||
jid=jid,
|
||||
resource=resource))
|
||||
|
||||
event = MessageReceivedEvent(None, **event_attr)
|
||||
app.nec.push_incoming_event(event)
|
||||
|
||||
|
|
|
@ -175,6 +175,10 @@ class Presence:
|
|||
def get_presence(self, to=None, typ=None, priority=None,
|
||||
show=None, status=None, nick=None, caps=True,
|
||||
sign=None, idle_time=None):
|
||||
if show not in ('chat', 'away', 'xa', 'dnd'):
|
||||
# Gajim sometimes passes invalid show values here
|
||||
# until this is fixed this is a workaround
|
||||
show = None
|
||||
presence = nbxmpp.Presence(to, typ, priority, show, status)
|
||||
if nick is not None:
|
||||
nick_tag = presence.setTag('nick', namespace=nbxmpp.NS_NICK)
|
||||
|
|
|
@ -92,7 +92,10 @@ class Register:
|
|||
error = stanza.getErrorMsg()
|
||||
log.info('Error: %s', error)
|
||||
if error_cb() is not None:
|
||||
error_cb()(error)
|
||||
form = is_form = None
|
||||
if stanza.getTagAttr('error', 'type') == 'modify':
|
||||
form, is_form = self._get_register_form(stanza)
|
||||
error_cb()(error, form, is_form)
|
||||
return
|
||||
|
||||
self._con.get_module('Presence').subscribe(agent, auto_auth=True)
|
||||
|
@ -116,8 +119,7 @@ class Register:
|
|||
iq, self._register_info_response, {'success_cb': weak_success_cb,
|
||||
'error_cb': weak_error_cb})
|
||||
|
||||
@staticmethod
|
||||
def _register_info_response(_con, stanza, success_cb, error_cb):
|
||||
def _register_info_response(self, _con, stanza, success_cb, error_cb):
|
||||
if not nbxmpp.isResultNode(stanza):
|
||||
error = stanza.getErrorMsg()
|
||||
log.info('Error: %s', error)
|
||||
|
@ -125,18 +127,28 @@ class Register:
|
|||
error_cb()(error)
|
||||
else:
|
||||
log.info('Register form received')
|
||||
form = stanza.getQuery().getTag('x', namespace=nbxmpp.NS_DATA)
|
||||
is_form = form is not None
|
||||
if not is_form:
|
||||
form = {}
|
||||
for field in stanza.getQueryPayload():
|
||||
if not isinstance(field, nbxmpp.Node):
|
||||
continue
|
||||
form[field.getName()] = field.getData()
|
||||
|
||||
if success_cb() is not None:
|
||||
form, is_form = self._get_register_form(stanza)
|
||||
success_cb()(form, is_form)
|
||||
|
||||
@staticmethod
|
||||
def _get_register_form(stanza):
|
||||
query = stanza.getTag('query')
|
||||
if not query:
|
||||
return None, False
|
||||
|
||||
form = query.getTag('x', namespace=nbxmpp.NS_DATA)
|
||||
is_form = form is not None
|
||||
if not is_form:
|
||||
form = {}
|
||||
for field in query.getPayload():
|
||||
if not isinstance(field, nbxmpp.Node):
|
||||
continue
|
||||
form[field.getName()] = field.getData()
|
||||
|
||||
return form, is_form
|
||||
|
||||
|
||||
def get_instance(*args, **kwargs):
|
||||
return Register(*args, **kwargs), 'Register'
|
||||
|
|
|
@ -27,6 +27,7 @@ import os
|
|||
import sys
|
||||
import re
|
||||
import logging
|
||||
from pathlib import Path
|
||||
|
||||
from gajim.common import app
|
||||
from gajim.common import caps_cache
|
||||
|
@ -104,25 +105,22 @@ class OptionsParser:
|
|||
fd.write(s + ' = ' + value + '\n')
|
||||
|
||||
def write(self):
|
||||
(base_dir, filename) = os.path.split(self.__filename)
|
||||
self.__tempfile = os.path.join(base_dir, '.' + filename)
|
||||
config_path = Path(self.__filename)
|
||||
tempfile = 'temp_%s' % config_path.name
|
||||
temp_filepath = config_path.parent / tempfile
|
||||
try:
|
||||
with open(self.__tempfile, 'w', encoding='utf-8') as f:
|
||||
app.config.foreach(self.write_line, f)
|
||||
except IOError as e:
|
||||
return str(e)
|
||||
with open(str(temp_filepath), 'w', encoding='utf-8') as file:
|
||||
app.config.foreach(self.write_line, file)
|
||||
except IOError:
|
||||
log.exception('Failed to write config file')
|
||||
return
|
||||
|
||||
if os.path.exists(self.__filename):
|
||||
if os.name == 'nt':
|
||||
# win32 needs this
|
||||
try:
|
||||
os.remove(self.__filename)
|
||||
except Exception as e:
|
||||
return str(e)
|
||||
try:
|
||||
os.rename(self.__tempfile, self.__filename)
|
||||
except IOError as e:
|
||||
return str(e)
|
||||
temp_filepath.replace(config_path)
|
||||
except Exception:
|
||||
log.exception('Failed to replace config file')
|
||||
else:
|
||||
log.info('Successful saved config file')
|
||||
|
||||
def update_config(self, old_version, new_version):
|
||||
old_version_list = old_version.split('.') # convert '0.x.y' to (0, x, y)
|
||||
|
|
|
@ -23,7 +23,7 @@ import struct
|
|||
import hashlib
|
||||
import os
|
||||
import time
|
||||
import platform
|
||||
import sys
|
||||
import logging
|
||||
from errno import EWOULDBLOCK
|
||||
from errno import ENOBUFS
|
||||
|
@ -296,7 +296,7 @@ class SocksQueue:
|
|||
def activate_proxy(self, idx):
|
||||
if not self.isHashInSockObjs(self.senders, idx):
|
||||
return
|
||||
for key in self.senders:
|
||||
for key in list(self.senders):
|
||||
if idx in key:
|
||||
sender = self.senders[key]
|
||||
if sender.file_props.type_ != 's':
|
||||
|
@ -687,13 +687,11 @@ class Socks5:
|
|||
OpenSSL.SSL.WantX509LookupError) as e:
|
||||
log.info('SSL rehandshake request: %s', repr(e))
|
||||
raise e
|
||||
except OpenSSL.SSL.SysCallError:
|
||||
return self._on_send_exception()
|
||||
except Exception as e:
|
||||
if e.errno not in (EINTR, ENOBUFS, EWOULDBLOCK):
|
||||
# peer stopped reading
|
||||
self.state = 8 # end connection
|
||||
self.disconnect()
|
||||
self.file_props.error = -1
|
||||
return -1
|
||||
return self._on_send_exception()
|
||||
self.size += lenn
|
||||
current_time = time.time()
|
||||
self.file_props.elapsed_time += current_time - \
|
||||
|
@ -719,6 +717,13 @@ class Socks5:
|
|||
self.disconnect()
|
||||
return -1
|
||||
|
||||
def _on_send_exception(self):
|
||||
# peer stopped reading
|
||||
self.state = 8 # end connection
|
||||
self.disconnect()
|
||||
self.file_props.error = -1
|
||||
return -1
|
||||
|
||||
def get_file_contents(self, timeout):
|
||||
"""
|
||||
Read file contents from socket and write them to file
|
||||
|
@ -1431,7 +1436,7 @@ class Socks5Listener(IdleObject):
|
|||
# Under windows Vista, we need that to listen on ipv6 AND ipv4
|
||||
# Doesn't work under windows XP
|
||||
if os.name == 'nt':
|
||||
if int(platform.win32_ver()[0]) >= 6: # Win Vista +
|
||||
if sys.getwindowsversion().major >= 6: # Win Vista +
|
||||
# 47 is socket.IPPROTO_IPV6
|
||||
# 27 is socket.IPV6_V6ONLY under windows, but not defined ...
|
||||
self._serv.setsockopt(41, 27, 0)
|
||||
|
|
|
@ -26,7 +26,6 @@ from gajim.common.zeroconf import zeroconf
|
|||
|
||||
from nbxmpp.protocol import *
|
||||
import socket
|
||||
import platform
|
||||
import ssl
|
||||
import errno
|
||||
import sys
|
||||
|
@ -73,7 +72,7 @@ class ZeroconfListener(IdleObject):
|
|||
self._serv.setsockopt(socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1)
|
||||
self._serv.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)
|
||||
if os.name == 'nt':
|
||||
if int(platform.win32_ver()[0]) >= 6: # Win Vista +
|
||||
if sys.getwindowsversion().major >= 6: # Win Vista +
|
||||
# 47 is socket.IPPROTO_IPV6
|
||||
# 27 is socket.IPV6_V6ONLY under windows, but not defined ...
|
||||
self._serv.setsockopt(41, 27, 0)
|
||||
|
|
|
@ -17,7 +17,6 @@
|
|||
|
||||
import logging
|
||||
import select
|
||||
import socket
|
||||
import re
|
||||
|
||||
from gajim.common.i18n import _
|
||||
|
@ -157,16 +156,16 @@ class Zeroconf:
|
|||
|
||||
self.queried.append(True)
|
||||
|
||||
def query_record_callback(self, sdRef, flags, interfaceIndex, errorCode,
|
||||
hosttarget, rrtype, rrclass, rdata, ttl):
|
||||
def getaddrinfo_callback(self, sdRef, flags, interfaceIndex, errorCode,
|
||||
hosttarget, address, ttl):
|
||||
if errorCode != pybonjour.kDNSServiceErr_NoError:
|
||||
log.error('Error in query_record_callback: %s', str(errorCode))
|
||||
log.error('Error in getaddrinfo_callback: %s', str(errorCode))
|
||||
return
|
||||
|
||||
fullname, port, txtRecord = self.resolved_contacts[hosttarget]
|
||||
|
||||
txt = pybonjour.TXTRecord.parse(txtRecord)
|
||||
ip = socket.inet_ntoa(rdata)
|
||||
ip = address[1]
|
||||
|
||||
name, bare_name, protocol, domain = self._parse_name(fullname)
|
||||
|
||||
|
@ -207,20 +206,18 @@ class Zeroconf:
|
|||
self.resolved_contacts[hosttarget] = (fullname, port, txtRecord)
|
||||
|
||||
try:
|
||||
query_sdRef = None
|
||||
query_sdRef = \
|
||||
pybonjour.DNSServiceQueryRecord(
|
||||
getaddrinfo_sdRef = \
|
||||
pybonjour.DNSServiceGetAddrInfo(
|
||||
interfaceIndex=interfaceIndex,
|
||||
fullname=hosttarget,
|
||||
rrtype=pybonjour.kDNSServiceType_A,
|
||||
callBack=self.query_record_callback)
|
||||
hostname=hosttarget,
|
||||
callBack=self.getaddrinfo_callback)
|
||||
|
||||
while not self.queried:
|
||||
ready = select.select([query_sdRef], [], [], resolve_timeout)
|
||||
if query_sdRef not in ready[0]:
|
||||
log.warning('Query record timed out')
|
||||
ready = select.select([getaddrinfo_sdRef], [], [], resolve_timeout)
|
||||
if getaddrinfo_sdRef not in ready[0]:
|
||||
log.warning('GetAddrInfo timed out')
|
||||
break
|
||||
pybonjour.DNSServiceProcessResult(query_sdRef)
|
||||
pybonjour.DNSServiceProcessResult(getaddrinfo_sdRef)
|
||||
else:
|
||||
self.queried.pop()
|
||||
|
||||
|
@ -231,8 +228,8 @@ class Zeroconf:
|
|||
self.error_CB(_('Error while adding service. %s') % error)
|
||||
|
||||
finally:
|
||||
if query_sdRef:
|
||||
query_sdRef.close()
|
||||
if getaddrinfo_sdRef:
|
||||
getaddrinfo_sdRef.close()
|
||||
|
||||
self.resolved.append(True)
|
||||
|
||||
|
|
|
@ -407,12 +407,13 @@
|
|||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkBox" id="banner_vbox">
|
||||
<object class="GtkGrid">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
|
||||
<property name="border_width">5</property>
|
||||
<property name="orientation">vertical</property>
|
||||
<property name="valign">center</property>
|
||||
<property name="margin_left">5</property>
|
||||
<property name="hexpand">True</property>
|
||||
<property name="row_spacing">2</property>
|
||||
<child>
|
||||
<object class="GtkLabel" id="banner_name_label">
|
||||
<property name="name">ChatControl-BannerNameLabel</property>
|
||||
|
@ -423,9 +424,9 @@
|
|||
<property name="xalign">0</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">True</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">0</property>
|
||||
<property name="left_attach">0</property>
|
||||
<property name="top_attach">0</property>
|
||||
<property name="width">2</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
|
@ -433,6 +434,7 @@
|
|||
<property name="name">ChatControl-BannerLabel</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="hexpand">True</property>
|
||||
<property name="label">label</property>
|
||||
<property name="use_markup">True</property>
|
||||
<property name="selectable">True</property>
|
||||
|
@ -440,14 +442,27 @@
|
|||
<signal name="populate-popup" handler="on_banner_label_populate_popup" swapped="no"/>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">True</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">1</property>
|
||||
<property name="left_attach">1</property>
|
||||
<property name="top_attach">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkImage" id="phone_image">
|
||||
<property name="can_focus">False</property>
|
||||
<property name="no_show_all">True</property>
|
||||
<property name="tooltip_text" translatable="yes">The last message was written on a mobile client</property>
|
||||
<property name="halign">start</property>
|
||||
<property name="margin_right">4</property>
|
||||
<property name="icon_name">phone-apple-iphone-symbolic</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="left_attach">0</property>
|
||||
<property name="top_attach">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">True</property>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
|
|
|
@ -311,6 +311,7 @@
|
|||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="halign">start</property>
|
||||
<property name="valign">center</property>
|
||||
<property name="label" translatable="yes">Chat</property>
|
||||
<style>
|
||||
<class name="dim-label"/>
|
||||
|
@ -326,6 +327,7 @@
|
|||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="tooltip_text" translatable="yes">Record history for this chat</property>
|
||||
<property name="valign">center</property>
|
||||
<signal name="notify::active" handler="on_log_history_checkbutton_toggled" swapped="no"/>
|
||||
</object>
|
||||
<packing>
|
||||
|
@ -337,6 +339,7 @@
|
|||
<object class="GtkLabel">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="valign">center</property>
|
||||
<property name="margin_left">6</property>
|
||||
<property name="label" translatable="yes">Record History</property>
|
||||
<style>
|
||||
|
@ -353,6 +356,7 @@
|
|||
<property name="width_request">400</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="valign">center</property>
|
||||
<property name="hexpand">True</property>
|
||||
<property name="model">liststore1</property>
|
||||
<property name="tearoff_title" translatable="yes">Ttitle</property>
|
||||
|
|
|
@ -1,29 +1,20 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!-- Generated with glade 3.20.1 -->
|
||||
<!-- Generated with glade 3.22.1 -->
|
||||
<interface>
|
||||
<requires lib="gtk+" version="3.14"/>
|
||||
<requires lib="gtk+" version="3.20"/>
|
||||
<object class="GtkBox" id="box">
|
||||
<property name="width_request">300</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="border_width">18</property>
|
||||
<property name="orientation">vertical</property>
|
||||
<property name="spacing">6</property>
|
||||
<child>
|
||||
<object class="GtkAlignment" id="alignment1">
|
||||
<object class="GtkImage">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="top_padding">8</property>
|
||||
<property name="bottom_padding">4</property>
|
||||
<property name="left_padding">8</property>
|
||||
<property name="right_padding">8</property>
|
||||
<child>
|
||||
<object class="GtkLabel" id="label">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<attributes>
|
||||
<attribute name="weight" value="bold"/>
|
||||
<attribute name="variant" value="normal"/>
|
||||
</attributes>
|
||||
</object>
|
||||
</child>
|
||||
<property name="icon_name">document-send-symbolic</property>
|
||||
<property name="icon_size">6</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
|
@ -32,21 +23,14 @@
|
|||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkAlignment" id="alignment2">
|
||||
<object class="GtkLabel" id="label">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="top_padding">4</property>
|
||||
<property name="bottom_padding">4</property>
|
||||
<property name="left_padding">8</property>
|
||||
<property name="right_padding">8</property>
|
||||
<child>
|
||||
<object class="GtkProgressBar" id="progressbar">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="pulse_step">0.10000000149</property>
|
||||
<property name="show_text">True</property>
|
||||
</object>
|
||||
</child>
|
||||
<property name="margin_top">6</property>
|
||||
<property name="label"><placeholder></property>
|
||||
<style>
|
||||
<class name="bold"/>
|
||||
</style>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
|
@ -54,5 +38,52 @@
|
|||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkLabel" id="progress_label">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="label"><progress></property>
|
||||
<style>
|
||||
<class name="dim-label"/>
|
||||
</style>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">2</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkProgressBar" id="progressbar">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="margin_top">6</property>
|
||||
<property name="pulse_step">0.10000000149</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">3</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkButton" id="cancel_upload_button">
|
||||
<property name="label" translatable="yes">Cancel Upload</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="receives_default">True</property>
|
||||
<property name="halign">center</property>
|
||||
<property name="margin_top">6</property>
|
||||
<signal name="clicked" handler="on_cancel_upload_button_clicked" swapped="no"/>
|
||||
<style>
|
||||
<class name="destructive-action"/>
|
||||
</style>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">4</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
</interface>
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!-- Generated with glade 3.22.0 -->
|
||||
<!-- Generated with glade 3.22.1 -->
|
||||
<interface>
|
||||
<requires lib="gtk+" version="3.20"/>
|
||||
<object class="GtkWindow" id="plugins_window">
|
||||
|
@ -13,6 +13,9 @@
|
|||
<property name="type_hint">dialog</property>
|
||||
<signal name="destroy" handler="on_plugins_window_destroy" swapped="no"/>
|
||||
<signal name="key-press-event" handler="on_key_press_event" swapped="no"/>
|
||||
<child type="titlebar">
|
||||
<placeholder/>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkNotebook" id="plugins_notebook">
|
||||
<property name="visible">True</property>
|
||||
|
@ -232,6 +235,7 @@
|
|||
<property name="wrap">True</property>
|
||||
<property name="wrap_mode">word-char</property>
|
||||
<property name="selectable">True</property>
|
||||
<property name="xalign">0</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
|
@ -375,9 +379,6 @@
|
|||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<child type="titlebar">
|
||||
<placeholder/>
|
||||
</child>
|
||||
</object>
|
||||
<object class="GtkTextBuffer" id="textbuffer1">
|
||||
<property name="text" translatable="yes">Plug-in decription should be displayed here. This text will be erased during PluginsWindow initialization.</property>
|
||||
|
|
|
@ -10,7 +10,8 @@
|
|||
<property name="can_focus">True</property>
|
||||
<property name="hscrollbar_policy">never</property>
|
||||
<property name="shadow_type">in</property>
|
||||
<property name="min_content_height">200</property>
|
||||
<property name="min_content_height">260</property>
|
||||
<property name="overlay_scrolling">False</property>
|
||||
<child>
|
||||
<object class="GtkViewport">
|
||||
<property name="visible">True</property>
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
"servers":[
|
||||
"0nl1ne.at",
|
||||
"404.city",
|
||||
"blabber.im",
|
||||
"brauchen.info",
|
||||
"chatme.im",
|
||||
"comm.unicate.me",
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
.gajim-incoming-nickname {
|
||||
color: rgb(164, 0, 0)
|
||||
color: rgb(207, 49, 47)
|
||||
}
|
||||
.gajim-outgoing-nickname {
|
||||
color: rgb(52, 101, 164)
|
||||
color: rgb(38, 139, 210)
|
||||
}
|
||||
.gajim-url {
|
||||
color: rgb(117, 80, 123)
|
||||
color: rgb(53, 132, 228)
|
||||
}
|
||||
.gajim-highlight-message {
|
||||
color: rgb(245, 121, 0)
|
||||
|
@ -15,4 +15,4 @@
|
|||
}
|
||||
.gajim-status-message {
|
||||
color: rgb(115, 210, 22)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -55,6 +55,7 @@ from gajim.common.exceptions import GajimGeneralException
|
|||
# Compat with Gajim 1.0.3 for plugins
|
||||
from gajim.gtk.dialogs import *
|
||||
from gajim.gtk.add_contact import AddNewContactWindow
|
||||
from gajim.gtk.util import get_builder
|
||||
|
||||
|
||||
log = logging.getLogger('gajim.dialogs')
|
||||
|
@ -1733,22 +1734,18 @@ class ProgressWindow(Gtk.ApplicationWindow):
|
|||
self.set_position(Gtk.WindowPosition.CENTER)
|
||||
self.set_show_menubar(False)
|
||||
self.set_title(_('File Transfer'))
|
||||
self.set_default_size(250, -1)
|
||||
|
||||
self.event = file.event
|
||||
self.file = file
|
||||
self.xml = gtkgui_helpers.get_gtk_builder(
|
||||
'httpupload_progress_dialog.ui')
|
||||
self._ui = get_builder('httpupload_progress_dialog.ui')
|
||||
|
||||
self.label = self.xml.get_object('label')
|
||||
self.progressbar = self.xml.get_object('progressbar')
|
||||
|
||||
self.add(self.xml.get_object('box'))
|
||||
self.add(self._ui.box)
|
||||
|
||||
self.pulse = GLib.timeout_add(100, self._pulse_progressbar)
|
||||
self.show_all()
|
||||
|
||||
self.connect('destroy', self._on_destroy)
|
||||
self._ui.connect_signals(self)
|
||||
app.ged.register_event_handler('httpupload-progress', ged.CORE,
|
||||
self._on_httpupload_progress)
|
||||
|
||||
|
@ -1756,20 +1753,23 @@ class ProgressWindow(Gtk.ApplicationWindow):
|
|||
if self.file != obj.file:
|
||||
return
|
||||
if obj.status == 'request':
|
||||
self.label.set_text(_('Requesting HTTP Upload Slot…'))
|
||||
self._ui.label.set_text(_('Requesting HTTP Upload Slot…'))
|
||||
elif obj.status == 'close':
|
||||
self.destroy()
|
||||
elif obj.status == 'upload':
|
||||
self.label.set_text(_('Uploading file via HTTP File Upload…'))
|
||||
self._ui.label.set_text(_('Uploading file via HTTP File Upload…'))
|
||||
elif obj.status == 'update':
|
||||
self.update_progress(obj.seen, obj.total)
|
||||
elif obj.status == 'encrypt':
|
||||
self.label.set_text(_('Encrypting file…'))
|
||||
self._ui.label.set_text(_('Encrypting file…'))
|
||||
|
||||
def _pulse_progressbar(self):
|
||||
self.progressbar.pulse()
|
||||
self._ui.progressbar.pulse()
|
||||
return True
|
||||
|
||||
def on_cancel_upload_button_clicked(self, widget):
|
||||
self.destroy()
|
||||
|
||||
def _on_destroy(self, *args):
|
||||
self.event.set()
|
||||
if self.pulse:
|
||||
|
@ -1783,6 +1783,9 @@ class ProgressWindow(Gtk.ApplicationWindow):
|
|||
if self.pulse:
|
||||
GLib.source_remove(self.pulse)
|
||||
self.pulse = None
|
||||
pct = (float(seen) / total) * 100.0
|
||||
self.progressbar.set_fraction(float(seen) / total)
|
||||
self.progressbar.set_text(str(int(pct)) + "%")
|
||||
self._ui.progressbar.set_fraction(float(seen) / total)
|
||||
size_total = round(total / (1024 * 1024), 1)
|
||||
size_progress = round(seen / (1024 * 1024), 1)
|
||||
self._ui.progress_label.set_text(
|
||||
_('%(progress)s of %(total)s MiB sent') % \
|
||||
{'progress': str(size_progress), 'total': str(size_total)})
|
||||
|
|
|
@ -27,7 +27,7 @@ from distutils.version import LooseVersion as V
|
|||
# Install _() in namespace
|
||||
from gajim.common import i18n
|
||||
|
||||
_MIN_NBXMPP_VER = "0.6.9"
|
||||
_MIN_NBXMPP_VER = "0.6.10"
|
||||
_MIN_GTK_VER = "3.22.0"
|
||||
|
||||
|
||||
|
|
|
@ -17,6 +17,8 @@
|
|||
|
||||
'''Window to create new post for discussion groups service.'''
|
||||
|
||||
import uuid
|
||||
|
||||
from gajim.common import app
|
||||
from nbxmpp import Node
|
||||
from gajim import gtkgui_helpers
|
||||
|
@ -65,7 +67,7 @@ class GroupsPostWindow:
|
|||
# publish it to node
|
||||
con = app.connections[self.account]
|
||||
con.get_module('PubSub').send_pb_publish(
|
||||
self.servicejid, self.groupid, item, '0')
|
||||
self.servicejid, self.groupid, item, str(uuid.uuid4()))
|
||||
|
||||
# close the window
|
||||
self.window.destroy()
|
||||
|
|
|
@ -564,6 +564,7 @@ class AccountCreationWizard:
|
|||
def create_vars(self, config):
|
||||
app.config.add_per('accounts', self.account)
|
||||
|
||||
config['account_label'] = '%s@%s' % (config['name'], config['hostname'])
|
||||
if not config['savepass']:
|
||||
config['password'] = ''
|
||||
|
||||
|
|
|
@ -33,7 +33,9 @@ from gajim.common import app
|
|||
from gajim.common import helpers
|
||||
from gajim.common import exceptions
|
||||
from gajim.common.i18n import _
|
||||
from gajim.common.const import ShowConstant, KindConstant
|
||||
from gajim.common.const import ShowConstant
|
||||
from gajim.common.const import KindConstant
|
||||
from gajim.common.const import StyleAttr
|
||||
|
||||
from gajim import conversation_textview
|
||||
|
||||
|
@ -79,7 +81,9 @@ class HistoryWindow:
|
|||
account, used_in_history_window=True)
|
||||
scrolledwindow.add(self.history_textview.tv)
|
||||
self.history_buffer = self.history_textview.tv.get_buffer()
|
||||
self.history_buffer.create_tag('highlight', background='yellow')
|
||||
highlight_color = app.css_config.get_value(
|
||||
'.gajim-highlight-message', StyleAttr.COLOR)
|
||||
self.history_buffer.create_tag('highlight', background=highlight_color)
|
||||
self.history_buffer.create_tag('invisible', invisible=True)
|
||||
self.checkbutton = xml.get_object('log_history_checkbutton')
|
||||
self.show_status_checkbutton = xml.get_object('show_status_checkbutton')
|
||||
|
|
|
@ -75,6 +75,14 @@ class ServiceRegistration(Gtk.Assistant):
|
|||
sidebar = main_box.get_children()[0]
|
||||
main_box.remove(sidebar)
|
||||
|
||||
def _build_dataform(self, form, is_form):
|
||||
if not is_form:
|
||||
from gajim import config
|
||||
return config.FakeDataForm(form)
|
||||
|
||||
dataform = dataforms.extend_form(node=form)
|
||||
return DataFormWidget(dataform)
|
||||
|
||||
def _on_page_change(self, assistant, page):
|
||||
if self.get_current_page() == Page.REQUEST:
|
||||
self._con.get_module('Register').get_register_form(
|
||||
|
@ -86,23 +94,22 @@ class ServiceRegistration(Gtk.Assistant):
|
|||
def _on_get_success(self, form, is_form):
|
||||
log.info('Show Form page')
|
||||
self._is_form = is_form
|
||||
if is_form:
|
||||
dataform = dataforms.extend_form(node=form)
|
||||
self._data_form_widget = DataFormWidget(dataform)
|
||||
else:
|
||||
from gajim import config
|
||||
self._data_form_widget = config.FakeDataForm(form)
|
||||
self._data_form_widget = self._build_dataform(form, is_form)
|
||||
|
||||
page = self.get_nth_page(Page.FORM)
|
||||
page.pack_start(self._data_form_widget, True, True, 0)
|
||||
self._data_form_widget.show_all()
|
||||
self.get_nth_page(Page.FORM).set_form(self._data_form_widget)
|
||||
self.set_current_page(Page.FORM)
|
||||
|
||||
def _on_error(self, error_text):
|
||||
log.info('Show Error page')
|
||||
page = self.get_nth_page(Page.ERROR)
|
||||
page.set_text(error_text)
|
||||
self.set_current_page(Page.ERROR)
|
||||
def _on_error(self, error_text, form=None, is_form=False):
|
||||
if form is not None:
|
||||
log.info('Show Form page')
|
||||
self._is_form = is_form
|
||||
self._data_form_widget = self._build_dataform(form, is_form)
|
||||
self.get_nth_page(Page.FORM).set_form(self._data_form_widget, error_text=error_text)
|
||||
self.set_current_page(Page.FORM)
|
||||
else:
|
||||
log.info('Show Error page')
|
||||
self.get_nth_page(Page.ERROR).set_text(error_text)
|
||||
self.set_current_page(Page.ERROR)
|
||||
|
||||
def _on_cancel(self, widget):
|
||||
self.destroy()
|
||||
|
@ -159,6 +166,25 @@ class FormPage(Gtk.Box):
|
|||
|
||||
def __init__(self):
|
||||
super().__init__(orientation=Gtk.Orientation.VERTICAL)
|
||||
self._form = None
|
||||
self._label = Gtk.Label()
|
||||
self._label.set_no_show_all(True)
|
||||
self._label.get_style_context().add_class('error-color')
|
||||
self.pack_end(self._label, False, False, 0)
|
||||
|
||||
def set_form(self, form, error_text=None):
|
||||
if self._form is not None:
|
||||
self.remove(self._form)
|
||||
self._form.destroy()
|
||||
self._label.hide()
|
||||
self._form = form
|
||||
|
||||
if error_text is not None:
|
||||
self._label.set_text(error_text)
|
||||
self._label.show()
|
||||
|
||||
self.pack_start(form, True, True, 0)
|
||||
self._form.show_all()
|
||||
|
||||
|
||||
class SuccessfulPage(Gtk.Box):
|
||||
|
|
|
@ -30,6 +30,7 @@ from gajim.common import app
|
|||
from gajim.common import configpaths
|
||||
from gajim.common import i18n
|
||||
from gajim.common.i18n import _
|
||||
from gajim.common.const import Display
|
||||
|
||||
_icon_theme = Gtk.IconTheme.get_default()
|
||||
_icon_theme.append_search_path(configpaths.get('ICONS'))
|
||||
|
@ -117,11 +118,17 @@ def get_iconset_name_for(name: str) -> str:
|
|||
|
||||
|
||||
def get_total_screen_geometry() -> Tuple[int, int]:
|
||||
screen = Gdk.Screen.get_default()
|
||||
window = Gdk.Screen.get_root_window(screen)
|
||||
width, height = window.get_width(), window.get_height()
|
||||
log.debug('Get screen geometry: %s %s', width, height)
|
||||
return width, height
|
||||
total_width = 0
|
||||
total_height = 0
|
||||
display = Gdk.Display.get_default()
|
||||
monitors = display.get_n_monitors()
|
||||
for num in range(0, monitors):
|
||||
monitor = display.get_monitor(num)
|
||||
geometry = monitor.get_geometry()
|
||||
total_width += geometry.width
|
||||
total_height = max(total_height, geometry.height)
|
||||
log.debug('Get screen geometry: %s %s', total_width, total_height)
|
||||
return total_width, total_height
|
||||
|
||||
|
||||
def resize_window(window: Gtk.Window, width: int, height: int) -> None:
|
||||
|
@ -155,6 +162,16 @@ def move_window(window: Gtk.Window, pos_x: int, pos_y: int) -> None:
|
|||
window.move(pos_x, pos_y)
|
||||
|
||||
|
||||
def restore_roster_position(window):
|
||||
if not app.config.get('save-roster-position'):
|
||||
return
|
||||
if app.is_display(Display.WAYLAND):
|
||||
return
|
||||
move_window(window,
|
||||
app.config.get('roster_x-position'),
|
||||
app.config.get('roster_y-position'))
|
||||
|
||||
|
||||
def get_completion_liststore(entry: Gtk.Entry) -> Gtk.ListStore:
|
||||
"""
|
||||
Create a completion model for entry widget completion list consists of
|
||||
|
|
|
@ -85,7 +85,7 @@ class ExceptionDialog():
|
|||
traceback.print_exception(type_, value, tb, None, trace)
|
||||
self.text = self.get_issue_text(trace.getvalue())
|
||||
buffer_.set_text(self.text)
|
||||
print(self.text)
|
||||
print(self.text, file=sys.stderr)
|
||||
self.exception_view.set_editable(False)
|
||||
self.dialog.show()
|
||||
if __name__ == '__main__':
|
||||
|
|
|
@ -17,8 +17,6 @@
|
|||
# You should have received a copy of the GNU General Public License
|
||||
# along with Gajim. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import gc
|
||||
|
||||
from gi.repository import Gtk
|
||||
from gi.repository import GLib
|
||||
from gi.repository import GObject
|
||||
|
@ -408,9 +406,6 @@ class MessageTextView(Gtk.TextView):
|
|||
self.add_child_at_anchor(image, anchor)
|
||||
buffer_.insert_at_cursor(' ')
|
||||
|
||||
def destroy(self):
|
||||
GLib.idle_add(gc.collect)
|
||||
|
||||
def clear(self, widget=None):
|
||||
"""
|
||||
Clear text in the textview
|
||||
|
|
|
@ -75,31 +75,10 @@ class Notification:
|
|||
Handle notifications
|
||||
"""
|
||||
def __init__(self):
|
||||
self._dbus_available = False
|
||||
self.daemon_capabilities = ['actions']
|
||||
|
||||
# Detect if actions are supported by the notification daemon
|
||||
if sys.platform not in ('win32', 'darwin'):
|
||||
def on_proxy_ready(source, res, data=None):
|
||||
try:
|
||||
proxy = Gio.DBusProxy.new_finish(res)
|
||||
self.daemon_capabilities = proxy.GetCapabilities()
|
||||
except GLib.Error as e:
|
||||
if e.domain == 'g-dbus-error-quark':
|
||||
log.info('Notifications D-Bus connection failed: %s',
|
||||
e.message)
|
||||
else:
|
||||
raise
|
||||
else:
|
||||
log.debug('Notifications D-Bus connected')
|
||||
|
||||
log.debug('Connecting to Notifications D-Bus')
|
||||
Gio.DBusProxy.new_for_bus(Gio.BusType.SESSION,
|
||||
Gio.DBusProxyFlags.DO_NOT_CONNECT_SIGNALS,
|
||||
None,
|
||||
'org.freedesktop.Notifications',
|
||||
'/org/freedesktop/Notifications',
|
||||
'org.freedesktop.Notifications',
|
||||
None, on_proxy_ready)
|
||||
self._detect_dbus_caps()
|
||||
|
||||
app.ged.register_event_handler(
|
||||
'notification', ged.GUI2, self._nec_notification)
|
||||
|
@ -107,6 +86,30 @@ class Notification:
|
|||
'our-show', ged.GUI2, self._nec_our_status)
|
||||
app.events.event_removed_subscribe(self._on_event_removed)
|
||||
|
||||
def _detect_dbus_caps(self):
|
||||
if sys.platform in ('win32', 'darwin'):
|
||||
return
|
||||
|
||||
def on_proxy_ready(_source, res, _data=None):
|
||||
try:
|
||||
proxy = Gio.DBusProxy.new_finish(res)
|
||||
self.daemon_capabilities = proxy.GetCapabilities()
|
||||
except GLib.Error:
|
||||
log.exception('Notifications D-Bus connection failed')
|
||||
else:
|
||||
self._dbus_available = True
|
||||
log.info('Notifications D-Bus connected')
|
||||
|
||||
log.info('Connecting to Notifications D-Bus')
|
||||
Gio.DBusProxy.new_for_bus(Gio.BusType.SESSION,
|
||||
Gio.DBusProxyFlags.DO_NOT_CONNECT_SIGNALS,
|
||||
None,
|
||||
'org.freedesktop.Notifications',
|
||||
'/org/freedesktop/Notifications',
|
||||
'org.freedesktop.Notifications',
|
||||
None,
|
||||
on_proxy_ready)
|
||||
|
||||
def _nec_notification(self, obj):
|
||||
if obj.do_popup:
|
||||
icon_name = self._get_icon_name(obj)
|
||||
|
@ -170,6 +173,9 @@ class Notification:
|
|||
app.interface.roster.popup_notification_windows.append(instance)
|
||||
return
|
||||
|
||||
if not self._dbus_available:
|
||||
return
|
||||
|
||||
scale = gtkgui_helpers.get_monitor_scale_factor()
|
||||
icon_pixbuf = gtkgui_helpers.gtk_icon_theme.load_icon_for_scale(
|
||||
icon_name, 48, scale, 0)
|
||||
|
@ -220,8 +226,9 @@ class Notification:
|
|||
app.app.send_notification(notif_id, notification)
|
||||
|
||||
def withdraw(self, *args):
|
||||
if sys.platform != 'win32':
|
||||
app.app.withdraw_notification(self._id(*args))
|
||||
if not self._dbus_available:
|
||||
return
|
||||
app.app.withdraw_notification(self._id(*args))
|
||||
|
||||
def _id(self, *args):
|
||||
return ','.join(args)
|
||||
|
|
|
@ -36,10 +36,12 @@ from gajim.gtk.dialogs import YesNoDialog
|
|||
from gajim.gtk.filechoosers import ArchiveChooserDialog
|
||||
from gajim.common import app
|
||||
from gajim.common import configpaths
|
||||
from gajim.common.exceptions import PluginsystemError
|
||||
from gajim.common.helpers import launch_browser_mailer
|
||||
|
||||
from gajim.plugins.helpers import log_calls
|
||||
from gajim.plugins.helpers import GajimPluginActivateException
|
||||
from gajim.plugins.plugins_i18n import _
|
||||
from gajim.common.exceptions import PluginsystemError
|
||||
|
||||
|
||||
@unique
|
||||
|
@ -63,14 +65,21 @@ class PluginsWindow:
|
|||
|
||||
widgets_to_extract = ('plugins_notebook', 'plugin_name_label',
|
||||
'plugin_version_label', 'plugin_authors_label',
|
||||
'plugin_homepage_linkbutton', 'uninstall_plugin_button',
|
||||
'configure_plugin_button', 'installed_plugins_treeview',
|
||||
'available_text', 'available_text_label')
|
||||
'plugin_homepage_linkbutton', 'install_plugin_button',
|
||||
'uninstall_plugin_button', 'configure_plugin_button',
|
||||
'installed_plugins_treeview', 'available_text',
|
||||
'available_text_label')
|
||||
|
||||
for widget_name in widgets_to_extract:
|
||||
setattr(self, widget_name, builder.get_object(widget_name))
|
||||
|
||||
self.plugin_description_textview = builder.get_object('description')
|
||||
|
||||
# Disable 'Install from ZIP' for Flatpak installs
|
||||
if app.is_flatpak():
|
||||
self.install_plugin_button.set_tooltip_text(
|
||||
_('Click to view Gajim\'s wiki page on how to install plugins in Flatpak.'))
|
||||
|
||||
self.installed_plugins_model = Gtk.ListStore(object, str, bool, bool,
|
||||
GdkPixbuf.Pixbuf)
|
||||
self.installed_plugins_treeview.set_model(self.installed_plugins_model)
|
||||
|
@ -237,6 +246,10 @@ class PluginsWindow:
|
|||
|
||||
@log_calls('PluginsWindow')
|
||||
def on_install_plugin_button_clicked(self, widget):
|
||||
if app.is_flatpak():
|
||||
launch_browser_mailer('url', 'https://dev.gajim.org/gajim/gajim/wikis/help/flathub')
|
||||
return
|
||||
|
||||
def show_warn_dialog():
|
||||
text = _('Archive is malformed')
|
||||
dialog = WarningDialog(text, '', transient_for=self.window)
|
||||
|
|
|
@ -406,9 +406,9 @@ class PluginManager(metaclass=Singleton):
|
|||
return
|
||||
for con in app.connections.values():
|
||||
for module in plugin.modules:
|
||||
instance, name = module.get_instance(con)
|
||||
if not module.zeroconf and con.name == 'Local':
|
||||
continue
|
||||
instance, name = module.get_instance(con)
|
||||
modules.register_single(con, instance, name)
|
||||
|
||||
# If handlers have been registered, register the
|
||||
|
@ -558,9 +558,9 @@ class PluginManager(metaclass=Singleton):
|
|||
return
|
||||
|
||||
for module in plugin.modules:
|
||||
instance, name = module.get_instance(con)
|
||||
if not module.zeroconf and con.name == 'Local':
|
||||
continue
|
||||
instance, name = module.get_instance(con)
|
||||
modules.register_single(con, instance, name)
|
||||
|
||||
def _plugin_is_active_in_global_config(self, plugin):
|
||||
|
|
|
@ -59,6 +59,7 @@ from gajim.common import helpers
|
|||
from gajim.common import idle
|
||||
from gajim.common.exceptions import GajimGeneralException
|
||||
from gajim.common import i18n
|
||||
from gajim.common.helpers import save_roster_position
|
||||
from gajim.common.i18n import _
|
||||
from gajim.common.const import PEPEventType, AvatarSize, StyleAttr
|
||||
from gajim.common.dbus import location
|
||||
|
@ -83,6 +84,8 @@ from gajim.gtk.service_registration import ServiceRegistration
|
|||
from gajim.gtk.history import HistoryWindow
|
||||
from gajim.gtk.accounts import AccountsWindow
|
||||
|
||||
from gajim.gtk.util import restore_roster_position
|
||||
|
||||
|
||||
log = logging.getLogger('gajim.roster')
|
||||
|
||||
|
@ -2407,11 +2410,7 @@ class RosterWindow:
|
|||
if not app.config.get('quit_on_roster_x_button') and (
|
||||
(app.interface.systray_enabled and app.config.get('trayicon') != \
|
||||
'on_event') or app.config.get('allow_hide_roster')):
|
||||
if app.config.get('save-roster-position'):
|
||||
x, y = self.window.get_position()
|
||||
log.debug('Save roster position (get_position): %s %s', x, y)
|
||||
app.config.set('roster_x-position', x)
|
||||
app.config.set('roster_y-position', y)
|
||||
save_roster_position(self.window)
|
||||
if os.name == 'nt' or app.config.get('hide_on_roster_x_button'):
|
||||
self.window.hide()
|
||||
else:
|
||||
|
@ -2436,11 +2435,7 @@ class RosterWindow:
|
|||
# in case show_roster_on_start is False and roster is never shown
|
||||
# window.window is None
|
||||
if self.window.get_window() is not None:
|
||||
if app.config.get('save-roster-position'):
|
||||
x, y = self.window.get_window().get_root_origin()
|
||||
log.debug('Save roster position (get_root_origin): %s %s', x, y)
|
||||
app.config.set('roster_x-position', x)
|
||||
app.config.set('roster_y-position', y)
|
||||
save_roster_position(self.window)
|
||||
width, height = self.window.get_size()
|
||||
app.config.set('roster_width', width)
|
||||
app.config.set('roster_height', height)
|
||||
|
@ -2501,8 +2496,11 @@ class RosterWindow:
|
|||
self.send_pep(acct, pep_dict)
|
||||
|
||||
def on_continue2(message, pep_dict):
|
||||
if 'file_transfers' not in app.interface.instances:
|
||||
on_continue3(message, pep_dict)
|
||||
return
|
||||
# check if there is an active file transfer
|
||||
from gajim.common.protocol.bytestream import (is_transfer_active)
|
||||
from gajim.common.protocol.bytestream import is_transfer_active
|
||||
files_props = app.interface.instances['file_transfers'].\
|
||||
files_props
|
||||
transfer_active = False
|
||||
|
@ -5703,13 +5701,11 @@ class RosterWindow:
|
|||
if len(app.connections) < 2:
|
||||
# Do not merge accounts if only one exists
|
||||
self.regroup = False
|
||||
|
||||
gtkgui_helpers.resize_window(self.window,
|
||||
app.config.get('roster_width'),
|
||||
app.config.get('roster_height'))
|
||||
if app.config.get('save-roster-position'):
|
||||
gtkgui_helpers.move_window(self.window,
|
||||
app.config.get('roster_x-position'),
|
||||
app.config.get('roster_y-position'))
|
||||
restore_roster_position(self.window)
|
||||
|
||||
self.popups_notification_height = 0
|
||||
self.popup_notification_windows = []
|
||||
|
|
|
@ -30,6 +30,8 @@ from gajim import gtkgui_helpers
|
|||
from gajim.common import app
|
||||
from gajim.common import helpers
|
||||
from gajim.common.i18n import _
|
||||
from gajim.common.helpers import save_roster_position
|
||||
from gajim.gtk.util import restore_roster_position
|
||||
from gajim.gtk.single_message import SingleMessageWindow
|
||||
|
||||
|
||||
|
@ -371,18 +373,11 @@ class StatusIcon:
|
|||
# No pending events, so toggle visible/hidden for roster window
|
||||
if win.get_property('visible'):
|
||||
if win.get_property('has-toplevel-focus') or os.name == 'nt':
|
||||
if app.config.get('save-roster-position'):
|
||||
x, y = win.get_position()
|
||||
app.config.set('roster_x-position', x)
|
||||
app.config.set('roster_y-position', y)
|
||||
save_roster_position(win)
|
||||
win.hide() # else we hide it from VD that was visible in
|
||||
else:
|
||||
if not win.get_property('visible'):
|
||||
win.show_all()
|
||||
if app.config.get('save-roster-position'):
|
||||
gtkgui_helpers.move_window(win,
|
||||
app.config.get('roster_x-position'),
|
||||
app.config.get('roster_y-position'))
|
||||
win.show_all()
|
||||
restore_roster_position(win)
|
||||
if not app.config.get('roster_window_skip_taskbar'):
|
||||
win.set_property('skip-taskbar-hint', False)
|
||||
win.present_with_time(Gtk.get_current_event_time())
|
||||
|
@ -394,11 +389,9 @@ class StatusIcon:
|
|||
if not event:
|
||||
return
|
||||
win = app.interface.roster.window
|
||||
if not win.get_property('visible') and app.config.get(
|
||||
'save-roster-position'):
|
||||
gtkgui_helpers.move_window(win,
|
||||
app.config.get('roster_x-position'),
|
||||
app.config.get('roster_y-position'))
|
||||
if not win.get_property('visible'):
|
||||
# Needed if we are in one window mode
|
||||
restore_roster_position(win)
|
||||
app.interface.handle_event(account, jid, event.type_)
|
||||
|
||||
def on_middle_click(self):
|
||||
|
|
|
@ -609,8 +609,7 @@ class FileTransfersTooltip():
|
|||
self.sid = sid
|
||||
return False, self.widget
|
||||
|
||||
@staticmethod
|
||||
def _create_tooltip(file_props, sid):
|
||||
def _create_tooltip(self, file_props, _sid):
|
||||
ft_table = Gtk.Table(2, 1)
|
||||
ft_table.set_property('column-spacing', 2)
|
||||
current_row = 1
|
||||
|
@ -642,26 +641,7 @@ class FileTransfersTooltip():
|
|||
if not transfered_len:
|
||||
transfered_len = 0
|
||||
properties.append((_('Transferred: '), helpers.convert_bytes(transfered_len)))
|
||||
status = ''
|
||||
if file_props.started:
|
||||
status = _('Not started')
|
||||
if file_props.stopped:
|
||||
status = _('Stopped')
|
||||
elif file_props.completed:
|
||||
status = _('Completed')
|
||||
elif not file_props.connected:
|
||||
if file_props.completed:
|
||||
status = _('Completed')
|
||||
else:
|
||||
if file_props.paused:
|
||||
status = Q_('?transfer status:Paused')
|
||||
elif file_props.stalled:
|
||||
# stalled is not paused. it is like 'frozen' it stopped alone
|
||||
status = _('Stalled')
|
||||
else:
|
||||
status = _('Transferring')
|
||||
else:
|
||||
status = _('Not started')
|
||||
status = self._get_current_status(file_props)
|
||||
properties.append((_('Status: '), status))
|
||||
file_desc = file_props.desc or ''
|
||||
properties.append((_('Description: '), GLib.markup_escape_text(
|
||||
|
@ -686,6 +666,24 @@ class FileTransfersTooltip():
|
|||
ft_table.show_all()
|
||||
return ft_table
|
||||
|
||||
@staticmethod
|
||||
def _get_current_status(file_props):
|
||||
if file_props.stopped:
|
||||
return _('Aborted')
|
||||
if file_props.completed:
|
||||
return _('Completed')
|
||||
if file_props.paused:
|
||||
return Q_('?transfer status:Paused')
|
||||
if file_props.stalled:
|
||||
# stalled is not paused. it is like 'frozen' it stopped alone
|
||||
return _('Stalled')
|
||||
|
||||
if file_props.connected:
|
||||
if file_props.started:
|
||||
return _('Transferring')
|
||||
return _('Not started')
|
||||
return _('Not started')
|
||||
|
||||
|
||||
def colorize_status(status):
|
||||
"""
|
||||
|
|
1517
po/be@latin.po
1517
po/be@latin.po
File diff suppressed because it is too large
Load Diff
1261
po/en_GB.po
1261
po/en_GB.po
File diff suppressed because it is too large
Load Diff
1211
po/nb_NO.po
1211
po/nb_NO.po
File diff suppressed because it is too large
Load Diff
1671
po/pt_BR.po
1671
po/pt_BR.po
File diff suppressed because it is too large
Load Diff
1261
po/sr@Latn.po
1261
po/sr@Latn.po
File diff suppressed because it is too large
Load Diff
2531
po/zh_CN.po
2531
po/zh_CN.po
File diff suppressed because it is too large
Load Diff
1261
po/zh_TW.po
1261
po/zh_TW.po
File diff suppressed because it is too large
Load Diff
|
@ -97,10 +97,11 @@ function install_deps {
|
|||
mingw-w64-"${ARCH}"-"${PYTHON_ID}"-pyopenssl \
|
||||
mingw-w64-"${ARCH}"-"${PYTHON_ID}"-docutils \
|
||||
mingw-w64-"${ARCH}"-"${PYTHON_ID}"-certifi \
|
||||
mingw-w64-"${ARCH}"-"${PYTHON_ID}"-six
|
||||
mingw-w64-"${ARCH}"-"${PYTHON_ID}"-six \
|
||||
mingw-w64-"${ARCH}"-"${PYTHON_ID}"-pygments
|
||||
|
||||
PIP_REQUIREMENTS="\
|
||||
nbxmpp==0.6.9
|
||||
git+https://dev.gajim.org/gajim/python-nbxmpp.git@nbxmpp_0.6
|
||||
git+https://dev.gajim.org/lovetox/pybonjour-python3.git
|
||||
git+https://github.com/enthought/pywin32-ctypes.git
|
||||
keyring
|
||||
|
|
Loading…
Reference in New Issue