Compare commits

...

52 Commits

Author SHA1 Message Date
'leftie c32f46e99f add plural-affirmative aliases for `/me` 2019-05-21 01:15:01 -04:00
Yann Leboulanger 257f784804 prepare 1.1.3 release 2019-04-23 21:25:11 +02:00
Yann Leboulanger b6b56a282e update translations 2019-04-23 19:23:51 +00:00
Philipp Hörist 9787729423 Update ChangeLog 2019-04-23 17:09:44 +02:00
Sebastiaan Lokhorst da04f7fe39 zeroconf_bonjour: switch from DNSServiceQueryRecord to DNSServiceGetAddrInfo for IPv6 compatibility 2019-04-23 17:09:44 +02:00
Alexander Bogdanov ff9afe194e Implementing error type=modify handling for XEP-0077 2019-04-16 18:33:32 +02:00
Philipp Hörist 9e0fb54173 Update ChangeLog 2019-04-09 22:30:31 +02:00
Philipp Hörist a7466406a5 Connect gpg agent action correctly
Fixes #9660
2019-04-09 22:28:35 +02:00
Philipp Hörist 27e2786bcc Update ChangeLog 2019-04-09 21:06:15 +02:00
Marcin Mielniczuk db942378a1 Add the possibility to paste as quote 2019-04-09 20:58:16 +02:00
Philipp Hörist bf804f18d4 Fix memory leak with spell checker
- Dont shadow widgets destroy()
- Dont bind spell checker to class attribute, this prevents its finalize

Fixes #8822
2019-04-09 20:57:47 +02:00
Philipp Hörist 5f562fb0ab Iterate safely over dict
Fixes #9633
2019-04-06 09:34:50 +02:00
Philipp Hörist 72b8c0ab7f Windows: Fix version comparison
Fixes #9643
2019-04-06 09:34:31 +02:00
Philipp Hörist 80f37bb3fa Windows: Dont override format region settings 2019-04-05 21:28:46 +02:00
Philipp Hörist cb4e60c481 Dont send invalid presence show value 2019-04-05 21:27:39 +02:00
Daniel Brötzmann e01e552f65 Rework HTTPUpload dialog 2019-04-05 21:19:31 +02:00
Philipp Hörist 366b3df235 Remove reimport 2019-03-26 00:03:36 +01:00
Philipp Hörist f9872d3abb Update ChangeLog 2019-03-25 23:59:07 +01:00
Philipp Hörist da5ffe4bcc Correctly get the total screen geometry
get_root_window() does not work on Wayland

Fixes #9637
2019-03-25 23:53:05 +01:00
Philipp Hörist 669671e0b2 Move imports to the top 2019-03-25 23:52:37 +01:00
Philipp Hörist 0e9422ae9b Refactor restoring roster position 2019-03-25 23:51:46 +01:00
Philipp Hörist 2f5d00d1f3 Refactor saving roster position
- Dont save roster position on Wayland
2019-03-25 23:46:53 +01:00
Philipp Hörist 23c08892a0 Add method to determine window manager 2019-03-25 23:30:10 +01:00
Daniel Brötzmann 838b04d6b6 Improve dark theme colors 2019-03-25 23:24:33 +01:00
Daniel Brötzmann 64bda9cf79 Flatpak: Disable install from ZIP 2019-03-25 23:19:11 +01:00
Dominion 40cf9488b7 Add server blabber.im to servers.json 2019-03-25 23:15:26 +01:00
André Apitzsch 65dc8ed892 Flatpak: Update dependencies 2019-03-17 00:09:46 +01:00
André Apitzsch 4099b01f9d Flatpak: Fix access to GnuPG keys
Fixes gajim/gajim-plugins#386
2019-03-16 23:59:15 +01:00
Philipp Hörist c972196a4f Update ChangeLog 2019-03-16 16:32:42 +01:00
Philipp Hörist 06ca9b39e1 Raise nbxmpp version 2019-03-16 16:21:53 +01:00
Philipp Hörist bb4daa2945 Generate account label correctly 2019-03-16 16:19:37 +01:00
Philipp Hörist d159a7a1e2 Fix error while quitting
Fixes #9622
2019-03-16 16:19:26 +01:00
Philipp Hörist 7ba9bc4b04 Dont leak DNS query if we connect via proxy
Fixes #9573
2019-03-16 16:19:11 +01:00
Philipp Hörist 76cb4d1d08 HistoryWindow: Center top widgets vertically 2019-03-16 16:18:49 +01:00
Philipp Hörist 3729b8f94f Themes: Better visibility of scrollbar 2019-03-16 16:17:05 +01:00
Philipp Hörist 36e80622bc Notifications: Check if DBus is available 2019-03-16 15:47:51 +01:00
Philipp Hörist c1948d05e0 Get module instance after checking for zeroconf 2019-03-16 15:38:19 +01:00
Philipp Hörist c0178db779 Redirect some print statments to stderr
Otherwise they errors will not show up if the console output is
redirected to a file
2019-03-02 10:52:30 +01:00
Philipp Hörist c5d2f8bdab Use pathlib replace() for saving config
This makes it hopefully more resilient on Windows
2019-03-02 10:47:34 +01:00
Philipp Hörist 56e40954b7 Use a UUID4 as item id for pubsub posts 2019-03-02 10:47:18 +01:00
André Apitzsch c4b5671b69 Flatpak: Switch to stable branch 2019-02-17 15:30:58 +01:00
André Apitzsch 0dfce19d22 Flatpak: update dependencies 2019-02-17 15:24:33 +01:00
Philipp Hörist f4b4e9cc88 Determine windows version reliably
Fixes #9578
2019-02-15 16:56:52 +01:00
Philipp Hörist 064f249c5a Add mobile phone indicator
Shows a mobile phone icon if the last message was received by a client
which identifies as phone
2019-02-15 16:45:58 +01:00
Philipp Hörist 4189d5b9c8 Fix filetransfer tooltip 2019-02-15 16:35:57 +01:00
Philipp Hörist a1d68677d0 Dont fail on urn:xmpp:hashes:1
Fixes #9514
2019-02-15 16:35:57 +01:00
Philipp Hörist 05f1c78098 XTLS: Fix endless loop on write error 2019-02-15 16:35:56 +01:00
Philipp Hörist 9419e8ddf7 Fix deprecation warning
set_cipher_list wants bytes
2019-02-15 16:35:44 +01:00
Philipp Hörist bf9aed69fe Windows: Add pygments to build
(cherry picked from commit 5059f72373)
2019-02-15 11:38:59 +01:00
Philipp Hörist 14b4488b07 Windows: Use HEAD of nbxmpp_0.6 branch 2019-01-21 22:47:14 +01:00
Philipp Hörist 14fb085766 Windows: Fix language detection 2019-01-19 19:58:15 +01:00
Yann Leboulanger 8c0776e53e update appveyor build script for version number 2019-01-15 21:48:29 +01:00
85 changed files with 26603 additions and 26277 deletions

View File

@ -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

View File

@ -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'))

View File

@ -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" />

View File

@ -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

View File

@ -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'):

View File

@ -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

View File

@ -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)

View File

@ -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):
"""

View File

@ -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)

View File

@ -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

View File

@ -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,

View File

@ -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()

View File

@ -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'),

View File

@ -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):
"""

View File

@ -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)

View File

@ -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

View File

@ -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,

View File

@ -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,

View File

@ -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:

View File

@ -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)

View File

@ -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)

View File

@ -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'

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -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>

View File

@ -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>

View File

@ -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">&lt;placeholder&gt;</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">&lt;progress&gt;</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>

View File

@ -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>

View File

@ -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>

View File

@ -2,6 +2,7 @@
"servers":[
"0nl1ne.at",
"404.city",
"blabber.im",
"brauchen.info",
"chatme.im",
"comm.unicate.me",

View File

@ -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)
}
}

View File

@ -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)})

View File

@ -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"

View File

@ -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()

View File

@ -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'] = ''

View File

@ -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')

View File

@ -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):

View File

@ -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

View File

@ -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__':

View File

@ -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

View File

@ -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)

View File

@ -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)

View File

@ -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):

View File

@ -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 = []

View File

@ -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):

View File

@ -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.po

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

1261
po/bg.po

File diff suppressed because it is too large Load Diff

1521
po/br.po

File diff suppressed because it is too large Load Diff

1184
po/ca.po

File diff suppressed because it is too large Load Diff

1261
po/cs.po

File diff suppressed because it is too large Load Diff

1261
po/da.po

File diff suppressed because it is too large Load Diff

1230
po/de.po

File diff suppressed because it is too large Load Diff

1523
po/el.po

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

1505
po/eo.po

File diff suppressed because it is too large Load Diff

3847
po/es.po

File diff suppressed because it is too large Load Diff

1535
po/eu.po

File diff suppressed because it is too large Load Diff

1645
po/fr.po

File diff suppressed because it is too large Load Diff

1531
po/gl.po

File diff suppressed because it is too large Load Diff

1211
po/he.po

File diff suppressed because it is too large Load Diff

1261
po/hr.po

File diff suppressed because it is too large Load Diff

1261
po/hu.po

File diff suppressed because it is too large Load Diff

1211
po/it.po

File diff suppressed because it is too large Load Diff

1211
po/ja.po

File diff suppressed because it is too large Load Diff

1261
po/kk.po

File diff suppressed because it is too large Load Diff

1238
po/lt.po

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

1230
po/nl.po

File diff suppressed because it is too large Load Diff

1261
po/pl.po

File diff suppressed because it is too large Load Diff

1947
po/pt.po

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

1211
po/ru.po

File diff suppressed because it is too large Load Diff

1261
po/sk.po

File diff suppressed because it is too large Load Diff

1261
po/sr.po

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

1261
po/sv.po

File diff suppressed because it is too large Load Diff

1243
po/tr.po

File diff suppressed because it is too large Load Diff

1211
po/uk.po

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -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