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) Gajim 1.1.2 (15 January 2019)
Bug fixes Bug fixes

View File

@ -32,8 +32,8 @@ build_script:
bash "git clone C:/projects/gajim C:/msys64/home/appveyor/gajim" bash "git clone C:/projects/gajim C:/msys64/home/appveyor/gajim"
bash "C:/msys64/home/appveyor/gajim/win/build.sh $($env:MSYS_ARCH)" 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.exe" -FileName "Gajim-1.1.2-$($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-Portable.exe" -FileName "Gajim-Portable-1.1.2-$($env:ARCH)-$($env:TIME_STRING).exe"
# on_finish: # on_finish:
# - ps: $blockRdp = $true; iex ((new-object net.webclient).DownloadString('https://raw.githubusercontent.com/appveyor/ci/master/scripts/enable-rdp.ps1')) # - 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_attribute id="money-gambling">none</content_attribute>
</content_rating> </content_rating>
<releases> <releases>
<release version="1.1.3" date="2019-04-23" />
<release version="1.1.2" date="2019-01-15" /> <release version="1.1.2" date="2019-01-15" />
<release version="1.1.1" date="2018-12-23" /> <release version="1.1.1" date="2018-12-23" />
<release version="1.1.0" date="2018-11-06" /> <release version="1.1.0" date="2018-11-06" />

View File

@ -3,8 +3,6 @@ runtime: org.gnome.Platform
runtime-version: 3.30 runtime-version: 3.30
sdk: org.gnome.Sdk sdk: org.gnome.Sdk
command: gajim command: gajim
tags: nightly
desktop-file-name-prefix: '(Nightly) '
finish-args: finish-args:
- --share=ipc - --share=ipc
- --share=network - --share=network
@ -22,6 +20,8 @@ finish-args:
- --filesystem=~/.config/dconf:ro - --filesystem=~/.config/dconf:ro
- --talk-name=ca.desrt.dconf - --talk-name=ca.desrt.dconf
- --env=DCONF_USER_CONFIG_DIR=.config/dconf - --env=DCONF_USER_CONFIG_DIR=.config/dconf
# GnuPG
- --filesystem=~/.gnupg
# extensions # extensions
- --env=PYTHONPATH=/app/plugins/lib/python3.7/site-packages - --env=PYTHONPATH=/app/plugins/lib/python3.7/site-packages
@ -62,8 +62,8 @@ modules:
- pip3 install --prefix=/app . - pip3 install --prefix=/app .
sources: sources:
- type: archive - type: archive
url: https://files.pythonhosted.org/packages/e7/a7/4cd50e57cc6f436f1cc3a7e8fa700ff9b8b4d471620629074913e3735fb2/cffi-1.11.5.tar.gz url: https://files.pythonhosted.org/packages/64/7c/27367b38e6cc3e1f49f193deb761fe75cda9f95da37b67b422e62281fcac/cffi-1.12.2.tar.gz
sha256: e90f17980e6ab0f3c2f3730e56d1fe9bcba1891eeea58966e89d352492cc74f4 sha256: e113878a446c6228669144ae8a56e268c91b7f1fafae927adc4879d9849e0ea7
- name: python3-asn1crypto - name: python3-asn1crypto
buildsystem: simple buildsystem: simple
@ -77,11 +77,11 @@ modules:
- name: python3-idna - name: python3-idna
buildsystem: simple buildsystem: simple
build-commands: 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: sources:
- type: file - type: file
url: https://files.pythonhosted.org/packages/4b/2a/0276479a4b3caeb8a8c1af2f8e4355746a97fab05a372e4a2c6a6b876165/idna-2.7-py2.py3-none-any.whl url: https://files.pythonhosted.org/packages/14/2c/cd551d81dbe15200be1cf41cd03869a46fe7226e7450af7a6545bfc474c9/idna-2.8-py2.py3-none-any.whl
sha256: 156a6814fb5ac1fc6850fb002e0852d56c0c8d2531923a51032d1b70760e186e sha256: ea8b7f6188e6fa117537c3df7da9fc686d485087abf6ac197f9c46432f7e4a3c
- name: python3-cryptography - name: python3-cryptography
buildsystem: simple buildsystem: simple
@ -89,17 +89,17 @@ modules:
- pip3 install --prefix=/app . - pip3 install --prefix=/app .
sources: sources:
- type: archive - type: archive
url: https://files.pythonhosted.org/packages/22/21/233e38f74188db94e8451ef6385754a98f3cad9b59bedf3a8e8b14988be4/cryptography-2.3.1.tar.gz url: https://files.pythonhosted.org/packages/07/ca/bc827c5e55918ad223d59d299fff92f3563476c3b00d0a9157d9c0217449/cryptography-2.6.1.tar.gz
sha256: 8d10113ca826a4c29d5b85b2c4e045ffa8bad74fb525ee0eceb1d38d4c70dfd6 sha256: 26c821cbeb683facb966045e2064303029d572a87ee69ca5a1bf54bf55f93ca6
- name: python3-pyopenssl - name: python3-pyopenssl
buildsystem: simple buildsystem: simple
build-commands: 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: sources:
- type: file - type: file
url: https://files.pythonhosted.org/packages/96/af/9d29e6bd40823061aea2e0574ccb2fcf72bfd6130ce53d32773ec375458c/pyOpenSSL-18.0.0-py2.py3-none-any.whl url: https://files.pythonhosted.org/packages/01/c8/ceb170d81bd3941cbeb9940fc6cc2ef2ca4288d0ca8929ea4db5905d904d/pyOpenSSL-19.0.0-py2.py3-none-any.whl
sha256: 26ff56a6b5ecaf3a2a59f132681e2a80afcc76b4f902f612f518f92c2a1bf854 sha256: c727930ad54b10fc157015014b666f2d8b41f70c0d03e83ab67624fd3dd5d1e6
- name: python3-dbus-python - name: python3-dbus-python
build-options: build-options:
@ -122,31 +122,31 @@ modules:
- name: python3-secretstorage - name: python3-secretstorage
buildsystem: simple buildsystem: simple
build-commands: 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: sources:
- type: file - type: file
url: https://files.pythonhosted.org/packages/d8/e8/80975fd281764c80b2eb581a7f25d2109786e273b8925e8161bd2d06d10a/SecretStorage-3.1.0-py3-none-any.whl url: https://files.pythonhosted.org/packages/82/59/cb226752e20d83598d7fdcabd7819570b0329a61db07cfbdd21b2ef546e3/SecretStorage-3.1.1-py3-none-any.whl
sha256: 20196abd1a9d1310df7573d58ca6e7ed9292218c98ca3638eea07beb16080343 sha256: 7a119fb52a88e398dbb22a4b3eb39b779bfbace7e4153b7bc6e5954d86282a8a
- name: python3-entrypoints - name: python3-entrypoints
buildsystem: simple buildsystem: simple
build-commands: 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: sources:
- type: file - type: file
url: https://files.pythonhosted.org/packages/cc/8b/4eefa9b47f1910b3d2081da67726b066e379b04ca897acfe9f92bac56147/entrypoints-0.2.3-py2.py3-none-any.whl url: https://files.pythonhosted.org/packages/ac/c6/44694103f8c221443ee6b0041f69e2740d89a25641e62fb4f2ee568f2f9c/entrypoints-0.3-py2.py3-none-any.whl
sha256: 10ad569bb245e7e2ba425285b9fa3e8178a0dc92fc53b1e1c553805e15a8825b sha256: 589f874b313739ad35be6e0cd7efde2a4e9b6fea91edcc34e58ecbb8dbe56d19
- name: python3-keyring - name: python3-keyring
buildsystem: simple buildsystem: simple
build-commands: 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: cleanup:
- /bin - /bin
sources: sources:
- type: file - type: file
url: https://files.pythonhosted.org/packages/5f/cb/dc7b2215cd82b77e7b8b48abd8989c1b09990d4c91a3ccfdc18a61157b36/keyring-16.0.2-py2.py3-none-any.whl url: https://files.pythonhosted.org/packages/a1/28/0058032477bfdf2003e605d175629963759220661615443e20711446bfa7/keyring-18.0.0-py2.py3-none-any.whl
sha256: 2a5cf5e596cbf8b66b98b8df2c214adfe21e6e18baa82006b2c482bd0c4be94c sha256: ca33f5ccc542b9ffaa196ee9a33488069e5e7eac77d5b81969f8a3ce74d0230c
- name: python3-cssutils - name: python3-cssutils
buildsystem: simple buildsystem: simple
@ -182,8 +182,8 @@ modules:
- pip3 install --prefix=/app . - pip3 install --prefix=/app .
sources: sources:
- type: archive - type: archive
url: https://files.pythonhosted.org/packages/24/54/23a475a0d7d3664ea21b14ce907245dc390496f31d229a9aac2ae20c7c28/nbxmpp-0.6.8.tar.gz url: https://files.pythonhosted.org/packages/d6/01/34b2a441926780f26edd21490158afe0eb76beae4efbb6bc4d3323eae69a/nbxmpp-0.6.10.tar.gz
sha256: 8c2b4b8aac1a8c6d07c1e30af542fde20a70a9b8c7c04017e9cea0db654437c6 sha256: cd73417777e4847fdd8d0d96c7cafc606952edbd2b9d52a2a72bb2aaa04d08ef
- name: gajim - name: gajim
buildsystem: simple buildsystem: simple
@ -193,5 +193,6 @@ modules:
sources: sources:
- type: git - type: git
url: https://dev.gajim.org/gajim/gajim.git url: https://dev.gajim.org/gajim/gajim.git
branch: gajim_1.1
post-install: post-install:
- install -d /app/plugins - install -d /app/plugins

View File

@ -1,7 +1,7 @@
import os import os
import subprocess import subprocess
__version__ = "1.1.2" __version__ = "1.1.3"
IS_FLATPAK = False IS_FLATPAK = False
if os.path.exists('/app/share/run-as-flatpak'): if os.path.exists('/app/share/run-as-flatpak'):

View File

@ -379,6 +379,7 @@ class GajimApplication(Gtk.Application):
act = Gio.SimpleAction.new_stateful( act = Gio.SimpleAction.new_stateful(
'agent', None, 'agent', None,
GLib.Variant.new_boolean(app.config.get('use_gpg_agent'))) GLib.Variant.new_boolean(app.config.get('use_gpg_agent')))
act.connect('change-state', app_actions.on_use_pgp_agent)
self.add_action(act) self.add_action(act)
# General Actions # General Actions

View File

@ -225,6 +225,9 @@ class ChatControl(ChatControlBase):
app.ged.register_event_handler('pep-received', ged.GUI1, app.ged.register_event_handler('pep-received', ged.GUI1,
self._nec_pep_received) 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: if self.TYPE_ID == message_control.TYPE_CHAT:
# Dont connect this when PrivateChatControl is used # Dont connect this when PrivateChatControl is used
app.ged.register_event_handler('update-roster-avatar', ged.GUI1, app.ged.register_event_handler('update-roster-avatar', ged.GUI1,
@ -435,6 +438,17 @@ class ChatControl(ChatControlBase):
else: else:
self.update_pep(obj.pep_type) 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): def _update_jingle(self, jingle_type):
if jingle_type not in ('audio', 'video'): if jingle_type not in ('audio', 'video'):
return return
@ -1063,6 +1077,9 @@ class ChatControl(ChatControlBase):
app.ged.remove_event_handler('pep-received', ged.GUI1, app.ged.remove_event_handler('pep-received', ged.GUI1,
self._nec_pep_received) 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: if self.TYPE_ID == message_control.TYPE_CHAT:
app.ged.remove_event_handler('update-roster-avatar', ged.GUI1, app.ged.remove_event_handler('update-roster-avatar', ged.GUI1,
self._nec_update_avatar) self._nec_update_avatar)

View File

@ -323,7 +323,6 @@ class ChatControlBase(MessageControl, ChatCommandProcessor, CommandTools):
self.set_emoticon_popover() self.set_emoticon_popover()
# Attach speller # Attach speller
self.spell_checker = None
self.set_speller() self.set_speller()
self.conv_textview.tv.show() self.conv_textview.tv.show()
@ -474,15 +473,15 @@ class ChatControlBase(MessageControl, ChatCommandProcessor, CommandTools):
if gspell_lang is None: if gspell_lang is None:
return 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( spell_buffer = Gspell.TextBuffer.get_from_gtk_text_buffer(
self.msg_textview.get_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 = Gspell.TextView.get_from_gtk_text_view(self.msg_textview)
spell_view.set_inline_spell_checking(False) spell_view.set_inline_spell_checking(False)
spell_view.set_enable_language_menu(True) 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): def get_speller_language(self):
per_type = 'contacts' per_type = 'contacts'
@ -560,14 +559,27 @@ class ChatControlBase(MessageControl, ChatCommandProcessor, CommandTools):
id_ = item.connect('activate', self.msg_textview.clear) id_ = item.connect('activate', self.msg_textview.clear)
self.handlers[id_] = item 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() menu.show_all()
def on_quote(self, widget, text): def insert_as_quote(self, text: str) -> None:
self.msg_textview.remove_placeholder() 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 = self.msg_textview.get_buffer()
message_buffer.insert_at_cursor(text) 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 # moved from ChatControl
def _on_banner_eventbox_button_press_event(self, widget, event): def _on_banner_eventbox_button_press_event(self, widget, event):
""" """

View File

@ -74,7 +74,7 @@ class StandardCommonCommands(CommandContainer):
def say(self, message): def say(self, message):
self.send(message) self.send(message)
@command(raw=True) @command('we','us',raw=True)
@doc(_("Send action (in the third person) to the current chat")) @doc(_("Send action (in the third person) to the current chat"))
def me(self, action): def me(self, action):
self.send("/me %s" % action) self.send("/me %s" % action)

View File

@ -39,11 +39,13 @@ from distutils.version import LooseVersion as V
from collections import namedtuple from collections import namedtuple
import nbxmpp import nbxmpp
from gi.repository import Gdk
import gajim import gajim
from gajim.common import config as c_config from gajim.common import config as c_config
from gajim.common import configpaths from gajim.common import configpaths
from gajim.common import ged as ged_module from gajim.common import ged as ged_module
from gajim.common.const import Display
from gajim.common.contacts import LegacyContactsAPI from gajim.common.contacts import LegacyContactsAPI
from gajim.common.events import Events from gajim.common.events import Events
from gajim.common.types import NetworkEventsControllerT # pylint: disable=unused-import from gajim.common.types import NetworkEventsControllerT # pylint: disable=unused-import
@ -148,6 +150,7 @@ socks5queue = None
gupnp_igd = None gupnp_igd = None
gajim_identity = {'type': 'pc', 'category': 'client', 'name': 'Gajim'} gajim_identity = {'type': 'pc', 'category': 'client', 'name': 'Gajim'}
gajim_common_features = [nbxmpp.NS_BYTESTREAM, nbxmpp.NS_SI, nbxmpp.NS_FILE, 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, nbxmpp.NS_MUC_USER, nbxmpp.NS_MUC_ADMIN, nbxmpp.NS_MUC_OWNER,
nbxmpp.NS_MUC_CONFIG, nbxmpp.NS_COMMANDS, nbxmpp.NS_DISCO_INFO, 'ipv6', nbxmpp.NS_MUC_CONFIG, nbxmpp.NS_COMMANDS, nbxmpp.NS_DISCO_INFO, 'ipv6',
@ -192,6 +195,18 @@ def is_installed(dependency):
def is_flatpak(): def is_flatpak():
return gajim.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): def disable_dependency(dependency):
_dependencies[dependency] = False _dependencies[dependency] = False

View File

@ -77,6 +77,14 @@ def client_supports(client_caps, requested_feature):
return requested_feature not in FEATURE_BLACKLIST return requested_feature not in FEATURE_BLACKLIST
return False 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): def create_suitable_client_caps(node, caps_hash, hash_method, fjid=None):
""" """
Create and return a suitable ClientCaps object for the given node, Create and return a suitable ClientCaps object for the given node,

View File

@ -905,10 +905,6 @@ class Connection(CommonConnection, ConnectionHandlers):
] ]
self._hostname = hostname 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: if use_srv and self._proxy is None:
self._srv_hosts = [] self._srv_hosts = []
@ -918,6 +914,9 @@ class Connection(CommonConnection, ConnectionHandlers):
for service in services: for service in services:
record_name = '_' + service + '._tcp.' + helpers.idn_to_ascii(h) record_name = '_' + service + '._tcp.' + helpers.idn_to_ascii(h)
app.resolver.resolve(record_name, self._on_resolve_srv) 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: else:
self._connect_to_next_host() self._connect_to_next_host()

View File

@ -193,6 +193,13 @@ class SyncThreshold(IntEnum):
return str(self.value) return str(self.value)
class Display(Enum):
X11 = 'X11Display'
WAYLAND = 'GdkWaylandDisplay'
WIN32 = 'GdkWin32Display'
QUARTZ = 'GdkQuartzDisplay'
ACTIVITIES = { ACTIVITIES = {
'doing_chores': { 'doing_chores': {
'category': _('Doing Chores'), 'category': _('Doing Chores'),

View File

@ -116,6 +116,10 @@ class CommonContact(XMPPEntity):
return False return False
return caps_cache.client_supports(self.client_caps, requested_feature) 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): class Contact(CommonContact):
""" """

View File

@ -42,6 +42,7 @@ import logging
import json import json
import shutil import shutil
import collections import collections
from io import StringIO
from datetime import datetime, timedelta from datetime import datetime, timedelta
from distutils.version import LooseVersion as V from distutils.version import LooseVersion as V
from encodings.punycode import punycode_encode from encodings.punycode import punycode_encode
@ -51,11 +52,17 @@ import nbxmpp
from nbxmpp.stringprepare import nameprep from nbxmpp.stringprepare import nameprep
import precis_i18n.codec # pylint: disable=unused-import import precis_i18n.codec # pylint: disable=unused-import
from gajim.common import app
from gajim.common import caps_cache from gajim.common import caps_cache
from gajim.common import configpaths from gajim.common import configpaths
from gajim.common.i18n import Q_ from gajim.common.i18n import Q_
from gajim.common.i18n import _ from gajim.common.i18n import _
from gajim.common.i18n import ngettext 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') log = logging.getLogger('gajim.c.helpers')
@ -552,12 +559,6 @@ def datetime_tuple(timestamp):
tim = tim.timetuple() tim = tim.timetuple()
return tim return tim
from gajim.common import app
if app.is_installed('PYCURL'):
import pycurl
from io import StringIO
def convert_bytes(string): def convert_bytes(string):
suffix = '' suffix = ''
# IEC standard says KiB = 1024 bytes KB = 1000 bytes # IEC standard says KiB = 1024 bytes KB = 1000 bytes
@ -1534,3 +1535,14 @@ class AdditionalDataDict(collections.UserDict):
del _dict[key] del _dict[key]
except KeyError: except KeyError:
return 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: try:
locale.setlocale(locale.LC_ALL, '') locale.setlocale(locale.LC_ALL, '')
except locale.Error as error: except locale.Error as error:
print(error) print(error, file=sys.stderr)
try: try:
LANG = get_default_lang() 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: except Exception as error:
print('Failed to determine default language') print('Failed to determine default language', file=sys.stderr)
import traceback import traceback
traceback.print_exc() traceback.print_exc()
@ -173,6 +178,6 @@ for dir_ in iter_locale_dirs():
else: else:
break break
else: else:
print('No translations found') print('No translations found', file=sys.stderr)
print('Dirs searched: %s' % get_locale_dirs()) print('Dirs searched: %s' % get_locale_dirs(), file=sys.stderr)
_ = _translation.gettext _ = _translation.gettext

View File

@ -445,6 +445,8 @@ class JingleSession:
if child.getName() == 'checksum': if child.getName() == 'checksum':
hash_ = child.getTag('file').getTag(name='hash', hash_ = child.getTag('file').getTag(name='hash',
namespace=nbxmpp.NS_HASHES_2) namespace=nbxmpp.NS_HASHES_2)
if hash_ is None:
continue
algo = hash_.getAttr('algo') algo = hash_.getAttr('algo')
if algo in nbxmpp.Hashes2.supported: if algo in nbxmpp.Hashes2.supported:
file_props = FilesProp.getFileProp(self.connection.name, 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 \ flags = (SSL.OP_NO_SSLv2 | SSL.OP_NO_SSLv3 | SSL.OP_SINGLE_DH_USE \
| SSL.OP_NO_TICKET) | SSL.OP_NO_TICKET)
ctx.set_options(flags) 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 if fingerprint == 'server': # for testing purposes only
ctx.set_verify(SSL.VERIFY_NONE|SSL.VERIFY_FAIL_IF_NO_PEER_CERT, 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) return int(arg)
if arg.isupper() and hasattr(logging, arg): if arg.isupper() and hasattr(logging, arg):
return getattr(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 return 0
def parseLogTarget(arg): def parseLogTarget(arg):
@ -69,7 +69,8 @@ def parseAndSetLogLevels(arg):
target = parseLogTarget(target.strip()) target = parseLogTarget(target.strip())
if target: if target:
logging.getLogger(target).setLevel(level) 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: class colors:

View File

@ -207,6 +207,11 @@ class Message:
'gc_control': gc_control '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) event = MessageReceivedEvent(None, **event_attr)
app.nec.push_incoming_event(event) app.nec.push_incoming_event(event)

View File

@ -175,6 +175,10 @@ class Presence:
def get_presence(self, to=None, typ=None, priority=None, def get_presence(self, to=None, typ=None, priority=None,
show=None, status=None, nick=None, caps=True, show=None, status=None, nick=None, caps=True,
sign=None, idle_time=None): 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) presence = nbxmpp.Presence(to, typ, priority, show, status)
if nick is not None: if nick is not None:
nick_tag = presence.setTag('nick', namespace=nbxmpp.NS_NICK) nick_tag = presence.setTag('nick', namespace=nbxmpp.NS_NICK)

View File

@ -92,7 +92,10 @@ class Register:
error = stanza.getErrorMsg() error = stanza.getErrorMsg()
log.info('Error: %s', error) log.info('Error: %s', error)
if error_cb() is not None: 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 return
self._con.get_module('Presence').subscribe(agent, auto_auth=True) 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, iq, self._register_info_response, {'success_cb': weak_success_cb,
'error_cb': weak_error_cb}) 'error_cb': weak_error_cb})
@staticmethod def _register_info_response(self, _con, stanza, success_cb, error_cb):
def _register_info_response(_con, stanza, success_cb, error_cb):
if not nbxmpp.isResultNode(stanza): if not nbxmpp.isResultNode(stanza):
error = stanza.getErrorMsg() error = stanza.getErrorMsg()
log.info('Error: %s', error) log.info('Error: %s', error)
@ -125,18 +127,28 @@ class Register:
error_cb()(error) error_cb()(error)
else: else:
log.info('Register form received') 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: if success_cb() is not None:
form, is_form = self._get_register_form(stanza)
success_cb()(form, is_form) 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): def get_instance(*args, **kwargs):
return Register(*args, **kwargs), 'Register' return Register(*args, **kwargs), 'Register'

View File

@ -27,6 +27,7 @@ import os
import sys import sys
import re import re
import logging import logging
from pathlib import Path
from gajim.common import app from gajim.common import app
from gajim.common import caps_cache from gajim.common import caps_cache
@ -104,25 +105,22 @@ class OptionsParser:
fd.write(s + ' = ' + value + '\n') fd.write(s + ' = ' + value + '\n')
def write(self): def write(self):
(base_dir, filename) = os.path.split(self.__filename) config_path = Path(self.__filename)
self.__tempfile = os.path.join(base_dir, '.' + filename) tempfile = 'temp_%s' % config_path.name
temp_filepath = config_path.parent / tempfile
try: try:
with open(self.__tempfile, 'w', encoding='utf-8') as f: with open(str(temp_filepath), 'w', encoding='utf-8') as file:
app.config.foreach(self.write_line, f) app.config.foreach(self.write_line, file)
except IOError as e: except IOError:
return str(e) 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: try:
os.rename(self.__tempfile, self.__filename) temp_filepath.replace(config_path)
except IOError as e: except Exception:
return str(e) log.exception('Failed to replace config file')
else:
log.info('Successful saved config file')
def update_config(self, old_version, new_version): def update_config(self, old_version, new_version):
old_version_list = old_version.split('.') # convert '0.x.y' to (0, x, y) 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 hashlib
import os import os
import time import time
import platform import sys
import logging import logging
from errno import EWOULDBLOCK from errno import EWOULDBLOCK
from errno import ENOBUFS from errno import ENOBUFS
@ -296,7 +296,7 @@ class SocksQueue:
def activate_proxy(self, idx): def activate_proxy(self, idx):
if not self.isHashInSockObjs(self.senders, idx): if not self.isHashInSockObjs(self.senders, idx):
return return
for key in self.senders: for key in list(self.senders):
if idx in key: if idx in key:
sender = self.senders[key] sender = self.senders[key]
if sender.file_props.type_ != 's': if sender.file_props.type_ != 's':
@ -687,13 +687,11 @@ class Socks5:
OpenSSL.SSL.WantX509LookupError) as e: OpenSSL.SSL.WantX509LookupError) as e:
log.info('SSL rehandshake request: %s', repr(e)) log.info('SSL rehandshake request: %s', repr(e))
raise e raise e
except OpenSSL.SSL.SysCallError:
return self._on_send_exception()
except Exception as e: except Exception as e:
if e.errno not in (EINTR, ENOBUFS, EWOULDBLOCK): if e.errno not in (EINTR, ENOBUFS, EWOULDBLOCK):
# peer stopped reading return self._on_send_exception()
self.state = 8 # end connection
self.disconnect()
self.file_props.error = -1
return -1
self.size += lenn self.size += lenn
current_time = time.time() current_time = time.time()
self.file_props.elapsed_time += current_time - \ self.file_props.elapsed_time += current_time - \
@ -719,6 +717,13 @@ class Socks5:
self.disconnect() self.disconnect()
return -1 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): def get_file_contents(self, timeout):
""" """
Read file contents from socket and write them to file 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 # Under windows Vista, we need that to listen on ipv6 AND ipv4
# Doesn't work under windows XP # Doesn't work under windows XP
if os.name == 'nt': 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 # 47 is socket.IPPROTO_IPV6
# 27 is socket.IPV6_V6ONLY under windows, but not defined ... # 27 is socket.IPV6_V6ONLY under windows, but not defined ...
self._serv.setsockopt(41, 27, 0) self._serv.setsockopt(41, 27, 0)

View File

@ -26,7 +26,6 @@ from gajim.common.zeroconf import zeroconf
from nbxmpp.protocol import * from nbxmpp.protocol import *
import socket import socket
import platform
import ssl import ssl
import errno import errno
import sys import sys
@ -73,7 +72,7 @@ class ZeroconfListener(IdleObject):
self._serv.setsockopt(socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1) self._serv.setsockopt(socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1)
self._serv.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1) self._serv.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)
if os.name == 'nt': 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 # 47 is socket.IPPROTO_IPV6
# 27 is socket.IPV6_V6ONLY under windows, but not defined ... # 27 is socket.IPV6_V6ONLY under windows, but not defined ...
self._serv.setsockopt(41, 27, 0) self._serv.setsockopt(41, 27, 0)

View File

@ -17,7 +17,6 @@
import logging import logging
import select import select
import socket
import re import re
from gajim.common.i18n import _ from gajim.common.i18n import _
@ -157,16 +156,16 @@ class Zeroconf:
self.queried.append(True) self.queried.append(True)
def query_record_callback(self, sdRef, flags, interfaceIndex, errorCode, def getaddrinfo_callback(self, sdRef, flags, interfaceIndex, errorCode,
hosttarget, rrtype, rrclass, rdata, ttl): hosttarget, address, ttl):
if errorCode != pybonjour.kDNSServiceErr_NoError: 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 return
fullname, port, txtRecord = self.resolved_contacts[hosttarget] fullname, port, txtRecord = self.resolved_contacts[hosttarget]
txt = pybonjour.TXTRecord.parse(txtRecord) txt = pybonjour.TXTRecord.parse(txtRecord)
ip = socket.inet_ntoa(rdata) ip = address[1]
name, bare_name, protocol, domain = self._parse_name(fullname) name, bare_name, protocol, domain = self._parse_name(fullname)
@ -207,20 +206,18 @@ class Zeroconf:
self.resolved_contacts[hosttarget] = (fullname, port, txtRecord) self.resolved_contacts[hosttarget] = (fullname, port, txtRecord)
try: try:
query_sdRef = None getaddrinfo_sdRef = \
query_sdRef = \ pybonjour.DNSServiceGetAddrInfo(
pybonjour.DNSServiceQueryRecord(
interfaceIndex=interfaceIndex, interfaceIndex=interfaceIndex,
fullname=hosttarget, hostname=hosttarget,
rrtype=pybonjour.kDNSServiceType_A, callBack=self.getaddrinfo_callback)
callBack=self.query_record_callback)
while not self.queried: while not self.queried:
ready = select.select([query_sdRef], [], [], resolve_timeout) ready = select.select([getaddrinfo_sdRef], [], [], resolve_timeout)
if query_sdRef not in ready[0]: if getaddrinfo_sdRef not in ready[0]:
log.warning('Query record timed out') log.warning('GetAddrInfo timed out')
break break
pybonjour.DNSServiceProcessResult(query_sdRef) pybonjour.DNSServiceProcessResult(getaddrinfo_sdRef)
else: else:
self.queried.pop() self.queried.pop()
@ -231,8 +228,8 @@ class Zeroconf:
self.error_CB(_('Error while adding service. %s') % error) self.error_CB(_('Error while adding service. %s') % error)
finally: finally:
if query_sdRef: if getaddrinfo_sdRef:
query_sdRef.close() getaddrinfo_sdRef.close()
self.resolved.append(True) self.resolved.append(True)

View File

@ -407,12 +407,13 @@
</packing> </packing>
</child> </child>
<child> <child>
<object class="GtkBox" id="banner_vbox"> <object class="GtkGrid">
<property name="visible">True</property> <property name="visible">True</property>
<property name="can_focus">False</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="valign">center</property>
<property name="border_width">5</property> <property name="margin_left">5</property>
<property name="orientation">vertical</property> <property name="hexpand">True</property>
<property name="row_spacing">2</property>
<child> <child>
<object class="GtkLabel" id="banner_name_label"> <object class="GtkLabel" id="banner_name_label">
<property name="name">ChatControl-BannerNameLabel</property> <property name="name">ChatControl-BannerNameLabel</property>
@ -423,9 +424,9 @@
<property name="xalign">0</property> <property name="xalign">0</property>
</object> </object>
<packing> <packing>
<property name="expand">True</property> <property name="left_attach">0</property>
<property name="fill">True</property> <property name="top_attach">0</property>
<property name="position">0</property> <property name="width">2</property>
</packing> </packing>
</child> </child>
<child> <child>
@ -433,6 +434,7 @@
<property name="name">ChatControl-BannerLabel</property> <property name="name">ChatControl-BannerLabel</property>
<property name="visible">True</property> <property name="visible">True</property>
<property name="can_focus">False</property> <property name="can_focus">False</property>
<property name="hexpand">True</property>
<property name="label">label</property> <property name="label">label</property>
<property name="use_markup">True</property> <property name="use_markup">True</property>
<property name="selectable">True</property> <property name="selectable">True</property>
@ -440,14 +442,27 @@
<signal name="populate-popup" handler="on_banner_label_populate_popup" swapped="no"/> <signal name="populate-popup" handler="on_banner_label_populate_popup" swapped="no"/>
</object> </object>
<packing> <packing>
<property name="expand">True</property> <property name="left_attach">1</property>
<property name="fill">True</property> <property name="top_attach">1</property>
<property name="position">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> </packing>
</child> </child>
</object> </object>
<packing> <packing>
<property name="expand">True</property> <property name="expand">False</property>
<property name="fill">True</property> <property name="fill">True</property>
<property name="position">1</property> <property name="position">1</property>
</packing> </packing>

View File

@ -311,6 +311,7 @@
<property name="visible">True</property> <property name="visible">True</property>
<property name="can_focus">False</property> <property name="can_focus">False</property>
<property name="halign">start</property> <property name="halign">start</property>
<property name="valign">center</property>
<property name="label" translatable="yes">Chat</property> <property name="label" translatable="yes">Chat</property>
<style> <style>
<class name="dim-label"/> <class name="dim-label"/>
@ -326,6 +327,7 @@
<property name="visible">True</property> <property name="visible">True</property>
<property name="can_focus">True</property> <property name="can_focus">True</property>
<property name="tooltip_text" translatable="yes">Record history for this chat</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"/> <signal name="notify::active" handler="on_log_history_checkbutton_toggled" swapped="no"/>
</object> </object>
<packing> <packing>
@ -337,6 +339,7 @@
<object class="GtkLabel"> <object class="GtkLabel">
<property name="visible">True</property> <property name="visible">True</property>
<property name="can_focus">False</property> <property name="can_focus">False</property>
<property name="valign">center</property>
<property name="margin_left">6</property> <property name="margin_left">6</property>
<property name="label" translatable="yes">Record History</property> <property name="label" translatable="yes">Record History</property>
<style> <style>
@ -353,6 +356,7 @@
<property name="width_request">400</property> <property name="width_request">400</property>
<property name="visible">True</property> <property name="visible">True</property>
<property name="can_focus">False</property> <property name="can_focus">False</property>
<property name="valign">center</property>
<property name="hexpand">True</property> <property name="hexpand">True</property>
<property name="model">liststore1</property> <property name="model">liststore1</property>
<property name="tearoff_title" translatable="yes">Ttitle</property> <property name="tearoff_title" translatable="yes">Ttitle</property>

View File

@ -1,29 +1,20 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<!-- Generated with glade 3.20.1 --> <!-- Generated with glade 3.22.1 -->
<interface> <interface>
<requires lib="gtk+" version="3.14"/> <requires lib="gtk+" version="3.20"/>
<object class="GtkBox" id="box"> <object class="GtkBox" id="box">
<property name="width_request">300</property>
<property name="visible">True</property> <property name="visible">True</property>
<property name="can_focus">False</property> <property name="can_focus">False</property>
<property name="border_width">18</property>
<property name="orientation">vertical</property> <property name="orientation">vertical</property>
<property name="spacing">6</property>
<child> <child>
<object class="GtkAlignment" id="alignment1"> <object class="GtkImage">
<property name="visible">True</property> <property name="visible">True</property>
<property name="can_focus">False</property> <property name="can_focus">False</property>
<property name="top_padding">8</property> <property name="icon_name">document-send-symbolic</property>
<property name="bottom_padding">4</property> <property name="icon_size">6</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>
</object> </object>
<packing> <packing>
<property name="expand">False</property> <property name="expand">False</property>
@ -32,21 +23,14 @@
</packing> </packing>
</child> </child>
<child> <child>
<object class="GtkAlignment" id="alignment2"> <object class="GtkLabel" id="label">
<property name="visible">True</property> <property name="visible">True</property>
<property name="can_focus">False</property> <property name="can_focus">False</property>
<property name="top_padding">4</property> <property name="margin_top">6</property>
<property name="bottom_padding">4</property> <property name="label">&lt;placeholder&gt;</property>
<property name="left_padding">8</property> <style>
<property name="right_padding">8</property> <class name="bold"/>
<child> </style>
<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>
</object> </object>
<packing> <packing>
<property name="expand">False</property> <property name="expand">False</property>
@ -54,5 +38,52 @@
<property name="position">1</property> <property name="position">1</property>
</packing> </packing>
</child> </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> </object>
</interface> </interface>

View File

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<!-- Generated with glade 3.22.0 --> <!-- Generated with glade 3.22.1 -->
<interface> <interface>
<requires lib="gtk+" version="3.20"/> <requires lib="gtk+" version="3.20"/>
<object class="GtkWindow" id="plugins_window"> <object class="GtkWindow" id="plugins_window">
@ -13,6 +13,9 @@
<property name="type_hint">dialog</property> <property name="type_hint">dialog</property>
<signal name="destroy" handler="on_plugins_window_destroy" swapped="no"/> <signal name="destroy" handler="on_plugins_window_destroy" swapped="no"/>
<signal name="key-press-event" handler="on_key_press_event" swapped="no"/> <signal name="key-press-event" handler="on_key_press_event" swapped="no"/>
<child type="titlebar">
<placeholder/>
</child>
<child> <child>
<object class="GtkNotebook" id="plugins_notebook"> <object class="GtkNotebook" id="plugins_notebook">
<property name="visible">True</property> <property name="visible">True</property>
@ -232,6 +235,7 @@
<property name="wrap">True</property> <property name="wrap">True</property>
<property name="wrap_mode">word-char</property> <property name="wrap_mode">word-char</property>
<property name="selectable">True</property> <property name="selectable">True</property>
<property name="xalign">0</property>
</object> </object>
<packing> <packing>
<property name="expand">False</property> <property name="expand">False</property>
@ -375,9 +379,6 @@
</child> </child>
</object> </object>
</child> </child>
<child type="titlebar">
<placeholder/>
</child>
</object> </object>
<object class="GtkTextBuffer" id="textbuffer1"> <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> <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="can_focus">True</property>
<property name="hscrollbar_policy">never</property> <property name="hscrollbar_policy">never</property>
<property name="shadow_type">in</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> <child>
<object class="GtkViewport"> <object class="GtkViewport">
<property name="visible">True</property> <property name="visible">True</property>

View File

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

View File

@ -1,11 +1,11 @@
.gajim-incoming-nickname { .gajim-incoming-nickname {
color: rgb(164, 0, 0) color: rgb(207, 49, 47)
} }
.gajim-outgoing-nickname { .gajim-outgoing-nickname {
color: rgb(52, 101, 164) color: rgb(38, 139, 210)
} }
.gajim-url { .gajim-url {
color: rgb(117, 80, 123) color: rgb(53, 132, 228)
} }
.gajim-highlight-message { .gajim-highlight-message {
color: rgb(245, 121, 0) color: rgb(245, 121, 0)
@ -15,4 +15,4 @@
} }
.gajim-status-message { .gajim-status-message {
color: rgb(115, 210, 22) 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 # Compat with Gajim 1.0.3 for plugins
from gajim.gtk.dialogs import * from gajim.gtk.dialogs import *
from gajim.gtk.add_contact import AddNewContactWindow from gajim.gtk.add_contact import AddNewContactWindow
from gajim.gtk.util import get_builder
log = logging.getLogger('gajim.dialogs') log = logging.getLogger('gajim.dialogs')
@ -1733,22 +1734,18 @@ class ProgressWindow(Gtk.ApplicationWindow):
self.set_position(Gtk.WindowPosition.CENTER) self.set_position(Gtk.WindowPosition.CENTER)
self.set_show_menubar(False) self.set_show_menubar(False)
self.set_title(_('File Transfer')) self.set_title(_('File Transfer'))
self.set_default_size(250, -1)
self.event = file.event self.event = file.event
self.file = file self.file = file
self.xml = gtkgui_helpers.get_gtk_builder( self._ui = get_builder('httpupload_progress_dialog.ui')
'httpupload_progress_dialog.ui')
self.label = self.xml.get_object('label') self.add(self._ui.box)
self.progressbar = self.xml.get_object('progressbar')
self.add(self.xml.get_object('box'))
self.pulse = GLib.timeout_add(100, self._pulse_progressbar) self.pulse = GLib.timeout_add(100, self._pulse_progressbar)
self.show_all() self.show_all()
self.connect('destroy', self._on_destroy) self.connect('destroy', self._on_destroy)
self._ui.connect_signals(self)
app.ged.register_event_handler('httpupload-progress', ged.CORE, app.ged.register_event_handler('httpupload-progress', ged.CORE,
self._on_httpupload_progress) self._on_httpupload_progress)
@ -1756,20 +1753,23 @@ class ProgressWindow(Gtk.ApplicationWindow):
if self.file != obj.file: if self.file != obj.file:
return return
if obj.status == 'request': 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': elif obj.status == 'close':
self.destroy() self.destroy()
elif obj.status == 'upload': 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': elif obj.status == 'update':
self.update_progress(obj.seen, obj.total) self.update_progress(obj.seen, obj.total)
elif obj.status == 'encrypt': elif obj.status == 'encrypt':
self.label.set_text(_('Encrypting file…')) self._ui.label.set_text(_('Encrypting file…'))
def _pulse_progressbar(self): def _pulse_progressbar(self):
self.progressbar.pulse() self._ui.progressbar.pulse()
return True return True
def on_cancel_upload_button_clicked(self, widget):
self.destroy()
def _on_destroy(self, *args): def _on_destroy(self, *args):
self.event.set() self.event.set()
if self.pulse: if self.pulse:
@ -1783,6 +1783,9 @@ class ProgressWindow(Gtk.ApplicationWindow):
if self.pulse: if self.pulse:
GLib.source_remove(self.pulse) GLib.source_remove(self.pulse)
self.pulse = None self.pulse = None
pct = (float(seen) / total) * 100.0 self._ui.progressbar.set_fraction(float(seen) / total)
self.progressbar.set_fraction(float(seen) / total) size_total = round(total / (1024 * 1024), 1)
self.progressbar.set_text(str(int(pct)) + "%") 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 # Install _() in namespace
from gajim.common import i18n from gajim.common import i18n
_MIN_NBXMPP_VER = "0.6.9" _MIN_NBXMPP_VER = "0.6.10"
_MIN_GTK_VER = "3.22.0" _MIN_GTK_VER = "3.22.0"

View File

@ -17,6 +17,8 @@
'''Window to create new post for discussion groups service.''' '''Window to create new post for discussion groups service.'''
import uuid
from gajim.common import app from gajim.common import app
from nbxmpp import Node from nbxmpp import Node
from gajim import gtkgui_helpers from gajim import gtkgui_helpers
@ -65,7 +67,7 @@ class GroupsPostWindow:
# publish it to node # publish it to node
con = app.connections[self.account] con = app.connections[self.account]
con.get_module('PubSub').send_pb_publish( con.get_module('PubSub').send_pb_publish(
self.servicejid, self.groupid, item, '0') self.servicejid, self.groupid, item, str(uuid.uuid4()))
# close the window # close the window
self.window.destroy() self.window.destroy()

View File

@ -564,6 +564,7 @@ class AccountCreationWizard:
def create_vars(self, config): def create_vars(self, config):
app.config.add_per('accounts', self.account) app.config.add_per('accounts', self.account)
config['account_label'] = '%s@%s' % (config['name'], config['hostname'])
if not config['savepass']: if not config['savepass']:
config['password'] = '' config['password'] = ''

View File

@ -33,7 +33,9 @@ from gajim.common import app
from gajim.common import helpers from gajim.common import helpers
from gajim.common import exceptions from gajim.common import exceptions
from gajim.common.i18n import _ 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 from gajim import conversation_textview
@ -79,7 +81,9 @@ class HistoryWindow:
account, used_in_history_window=True) account, used_in_history_window=True)
scrolledwindow.add(self.history_textview.tv) scrolledwindow.add(self.history_textview.tv)
self.history_buffer = self.history_textview.tv.get_buffer() 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.history_buffer.create_tag('invisible', invisible=True)
self.checkbutton = xml.get_object('log_history_checkbutton') self.checkbutton = xml.get_object('log_history_checkbutton')
self.show_status_checkbutton = xml.get_object('show_status_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] sidebar = main_box.get_children()[0]
main_box.remove(sidebar) 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): def _on_page_change(self, assistant, page):
if self.get_current_page() == Page.REQUEST: if self.get_current_page() == Page.REQUEST:
self._con.get_module('Register').get_register_form( self._con.get_module('Register').get_register_form(
@ -86,23 +94,22 @@ class ServiceRegistration(Gtk.Assistant):
def _on_get_success(self, form, is_form): def _on_get_success(self, form, is_form):
log.info('Show Form page') log.info('Show Form page')
self._is_form = is_form self._is_form = is_form
if is_form: self._data_form_widget = self._build_dataform(form, 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)
page = self.get_nth_page(Page.FORM) self.get_nth_page(Page.FORM).set_form(self._data_form_widget)
page.pack_start(self._data_form_widget, True, True, 0)
self._data_form_widget.show_all()
self.set_current_page(Page.FORM) self.set_current_page(Page.FORM)
def _on_error(self, error_text): def _on_error(self, error_text, form=None, is_form=False):
log.info('Show Error page') if form is not None:
page = self.get_nth_page(Page.ERROR) log.info('Show Form page')
page.set_text(error_text) self._is_form = is_form
self.set_current_page(Page.ERROR) 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): def _on_cancel(self, widget):
self.destroy() self.destroy()
@ -159,6 +166,25 @@ class FormPage(Gtk.Box):
def __init__(self): def __init__(self):
super().__init__(orientation=Gtk.Orientation.VERTICAL) 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): class SuccessfulPage(Gtk.Box):

View File

@ -30,6 +30,7 @@ from gajim.common import app
from gajim.common import configpaths from gajim.common import configpaths
from gajim.common import i18n from gajim.common import i18n
from gajim.common.i18n import _ from gajim.common.i18n import _
from gajim.common.const import Display
_icon_theme = Gtk.IconTheme.get_default() _icon_theme = Gtk.IconTheme.get_default()
_icon_theme.append_search_path(configpaths.get('ICONS')) _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]: def get_total_screen_geometry() -> Tuple[int, int]:
screen = Gdk.Screen.get_default() total_width = 0
window = Gdk.Screen.get_root_window(screen) total_height = 0
width, height = window.get_width(), window.get_height() display = Gdk.Display.get_default()
log.debug('Get screen geometry: %s %s', width, height) monitors = display.get_n_monitors()
return width, height 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: 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) 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: def get_completion_liststore(entry: Gtk.Entry) -> Gtk.ListStore:
""" """
Create a completion model for entry widget completion list consists of 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) traceback.print_exception(type_, value, tb, None, trace)
self.text = self.get_issue_text(trace.getvalue()) self.text = self.get_issue_text(trace.getvalue())
buffer_.set_text(self.text) buffer_.set_text(self.text)
print(self.text) print(self.text, file=sys.stderr)
self.exception_view.set_editable(False) self.exception_view.set_editable(False)
self.dialog.show() self.dialog.show()
if __name__ == '__main__': if __name__ == '__main__':

View File

@ -17,8 +17,6 @@
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with Gajim. If not, see <http://www.gnu.org/licenses/>. # along with Gajim. If not, see <http://www.gnu.org/licenses/>.
import gc
from gi.repository import Gtk from gi.repository import Gtk
from gi.repository import GLib from gi.repository import GLib
from gi.repository import GObject from gi.repository import GObject
@ -408,9 +406,6 @@ class MessageTextView(Gtk.TextView):
self.add_child_at_anchor(image, anchor) self.add_child_at_anchor(image, anchor)
buffer_.insert_at_cursor(' ') buffer_.insert_at_cursor(' ')
def destroy(self):
GLib.idle_add(gc.collect)
def clear(self, widget=None): def clear(self, widget=None):
""" """
Clear text in the textview Clear text in the textview

View File

@ -75,31 +75,10 @@ class Notification:
Handle notifications Handle notifications
""" """
def __init__(self): def __init__(self):
self._dbus_available = False
self.daemon_capabilities = ['actions'] self.daemon_capabilities = ['actions']
# Detect if actions are supported by the notification daemon self._detect_dbus_caps()
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)
app.ged.register_event_handler( app.ged.register_event_handler(
'notification', ged.GUI2, self._nec_notification) 'notification', ged.GUI2, self._nec_notification)
@ -107,6 +86,30 @@ class Notification:
'our-show', ged.GUI2, self._nec_our_status) 'our-show', ged.GUI2, self._nec_our_status)
app.events.event_removed_subscribe(self._on_event_removed) 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): def _nec_notification(self, obj):
if obj.do_popup: if obj.do_popup:
icon_name = self._get_icon_name(obj) icon_name = self._get_icon_name(obj)
@ -170,6 +173,9 @@ class Notification:
app.interface.roster.popup_notification_windows.append(instance) app.interface.roster.popup_notification_windows.append(instance)
return return
if not self._dbus_available:
return
scale = gtkgui_helpers.get_monitor_scale_factor() scale = gtkgui_helpers.get_monitor_scale_factor()
icon_pixbuf = gtkgui_helpers.gtk_icon_theme.load_icon_for_scale( icon_pixbuf = gtkgui_helpers.gtk_icon_theme.load_icon_for_scale(
icon_name, 48, scale, 0) icon_name, 48, scale, 0)
@ -220,8 +226,9 @@ class Notification:
app.app.send_notification(notif_id, notification) app.app.send_notification(notif_id, notification)
def withdraw(self, *args): def withdraw(self, *args):
if sys.platform != 'win32': if not self._dbus_available:
app.app.withdraw_notification(self._id(*args)) return
app.app.withdraw_notification(self._id(*args))
def _id(self, *args): def _id(self, *args):
return ','.join(args) return ','.join(args)

View File

@ -36,10 +36,12 @@ from gajim.gtk.dialogs import YesNoDialog
from gajim.gtk.filechoosers import ArchiveChooserDialog from gajim.gtk.filechoosers import ArchiveChooserDialog
from gajim.common import app from gajim.common import app
from gajim.common import configpaths 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 log_calls
from gajim.plugins.helpers import GajimPluginActivateException from gajim.plugins.helpers import GajimPluginActivateException
from gajim.plugins.plugins_i18n import _ from gajim.plugins.plugins_i18n import _
from gajim.common.exceptions import PluginsystemError
@unique @unique
@ -63,14 +65,21 @@ class PluginsWindow:
widgets_to_extract = ('plugins_notebook', 'plugin_name_label', widgets_to_extract = ('plugins_notebook', 'plugin_name_label',
'plugin_version_label', 'plugin_authors_label', 'plugin_version_label', 'plugin_authors_label',
'plugin_homepage_linkbutton', 'uninstall_plugin_button', 'plugin_homepage_linkbutton', 'install_plugin_button',
'configure_plugin_button', 'installed_plugins_treeview', 'uninstall_plugin_button', 'configure_plugin_button',
'available_text', 'available_text_label') 'installed_plugins_treeview', 'available_text',
'available_text_label')
for widget_name in widgets_to_extract: for widget_name in widgets_to_extract:
setattr(self, widget_name, builder.get_object(widget_name)) setattr(self, widget_name, builder.get_object(widget_name))
self.plugin_description_textview = builder.get_object('description') 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, self.installed_plugins_model = Gtk.ListStore(object, str, bool, bool,
GdkPixbuf.Pixbuf) GdkPixbuf.Pixbuf)
self.installed_plugins_treeview.set_model(self.installed_plugins_model) self.installed_plugins_treeview.set_model(self.installed_plugins_model)
@ -237,6 +246,10 @@ class PluginsWindow:
@log_calls('PluginsWindow') @log_calls('PluginsWindow')
def on_install_plugin_button_clicked(self, widget): 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(): def show_warn_dialog():
text = _('Archive is malformed') text = _('Archive is malformed')
dialog = WarningDialog(text, '', transient_for=self.window) dialog = WarningDialog(text, '', transient_for=self.window)

View File

@ -406,9 +406,9 @@ class PluginManager(metaclass=Singleton):
return return
for con in app.connections.values(): for con in app.connections.values():
for module in plugin.modules: for module in plugin.modules:
instance, name = module.get_instance(con)
if not module.zeroconf and con.name == 'Local': if not module.zeroconf and con.name == 'Local':
continue continue
instance, name = module.get_instance(con)
modules.register_single(con, instance, name) modules.register_single(con, instance, name)
# If handlers have been registered, register the # If handlers have been registered, register the
@ -558,9 +558,9 @@ class PluginManager(metaclass=Singleton):
return return
for module in plugin.modules: for module in plugin.modules:
instance, name = module.get_instance(con)
if not module.zeroconf and con.name == 'Local': if not module.zeroconf and con.name == 'Local':
continue continue
instance, name = module.get_instance(con)
modules.register_single(con, instance, name) modules.register_single(con, instance, name)
def _plugin_is_active_in_global_config(self, plugin): 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 import idle
from gajim.common.exceptions import GajimGeneralException from gajim.common.exceptions import GajimGeneralException
from gajim.common import i18n from gajim.common import i18n
from gajim.common.helpers import save_roster_position
from gajim.common.i18n import _ from gajim.common.i18n import _
from gajim.common.const import PEPEventType, AvatarSize, StyleAttr from gajim.common.const import PEPEventType, AvatarSize, StyleAttr
from gajim.common.dbus import location 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.history import HistoryWindow
from gajim.gtk.accounts import AccountsWindow from gajim.gtk.accounts import AccountsWindow
from gajim.gtk.util import restore_roster_position
log = logging.getLogger('gajim.roster') log = logging.getLogger('gajim.roster')
@ -2407,11 +2410,7 @@ class RosterWindow:
if not app.config.get('quit_on_roster_x_button') and ( if not app.config.get('quit_on_roster_x_button') and (
(app.interface.systray_enabled and app.config.get('trayicon') != \ (app.interface.systray_enabled and app.config.get('trayicon') != \
'on_event') or app.config.get('allow_hide_roster')): 'on_event') or app.config.get('allow_hide_roster')):
if app.config.get('save-roster-position'): save_roster_position(self.window)
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)
if os.name == 'nt' or app.config.get('hide_on_roster_x_button'): if os.name == 'nt' or app.config.get('hide_on_roster_x_button'):
self.window.hide() self.window.hide()
else: else:
@ -2436,11 +2435,7 @@ class RosterWindow:
# in case show_roster_on_start is False and roster is never shown # in case show_roster_on_start is False and roster is never shown
# window.window is None # window.window is None
if self.window.get_window() is not None: if self.window.get_window() is not None:
if app.config.get('save-roster-position'): save_roster_position(self.window)
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)
width, height = self.window.get_size() width, height = self.window.get_size()
app.config.set('roster_width', width) app.config.set('roster_width', width)
app.config.set('roster_height', height) app.config.set('roster_height', height)
@ -2501,8 +2496,11 @@ class RosterWindow:
self.send_pep(acct, pep_dict) self.send_pep(acct, pep_dict)
def on_continue2(message, 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 # 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 = app.interface.instances['file_transfers'].\
files_props files_props
transfer_active = False transfer_active = False
@ -5703,13 +5701,11 @@ class RosterWindow:
if len(app.connections) < 2: if len(app.connections) < 2:
# Do not merge accounts if only one exists # Do not merge accounts if only one exists
self.regroup = False self.regroup = False
gtkgui_helpers.resize_window(self.window, gtkgui_helpers.resize_window(self.window,
app.config.get('roster_width'), app.config.get('roster_width'),
app.config.get('roster_height')) app.config.get('roster_height'))
if app.config.get('save-roster-position'): restore_roster_position(self.window)
gtkgui_helpers.move_window(self.window,
app.config.get('roster_x-position'),
app.config.get('roster_y-position'))
self.popups_notification_height = 0 self.popups_notification_height = 0
self.popup_notification_windows = [] self.popup_notification_windows = []

View File

@ -30,6 +30,8 @@ from gajim import gtkgui_helpers
from gajim.common import app from gajim.common import app
from gajim.common import helpers from gajim.common import helpers
from gajim.common.i18n import _ 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 from gajim.gtk.single_message import SingleMessageWindow
@ -371,18 +373,11 @@ class StatusIcon:
# No pending events, so toggle visible/hidden for roster window # No pending events, so toggle visible/hidden for roster window
if win.get_property('visible'): if win.get_property('visible'):
if win.get_property('has-toplevel-focus') or os.name == 'nt': if win.get_property('has-toplevel-focus') or os.name == 'nt':
if app.config.get('save-roster-position'): save_roster_position(win)
x, y = win.get_position()
app.config.set('roster_x-position', x)
app.config.set('roster_y-position', y)
win.hide() # else we hide it from VD that was visible in win.hide() # else we hide it from VD that was visible in
else: else:
if not win.get_property('visible'): win.show_all()
win.show_all() restore_roster_position(win)
if 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 app.config.get('roster_window_skip_taskbar'): if not app.config.get('roster_window_skip_taskbar'):
win.set_property('skip-taskbar-hint', False) win.set_property('skip-taskbar-hint', False)
win.present_with_time(Gtk.get_current_event_time()) win.present_with_time(Gtk.get_current_event_time())
@ -394,11 +389,9 @@ class StatusIcon:
if not event: if not event:
return return
win = app.interface.roster.window win = app.interface.roster.window
if not win.get_property('visible') and app.config.get( if not win.get_property('visible'):
'save-roster-position'): # Needed if we are in one window mode
gtkgui_helpers.move_window(win, restore_roster_position(win)
app.config.get('roster_x-position'),
app.config.get('roster_y-position'))
app.interface.handle_event(account, jid, event.type_) app.interface.handle_event(account, jid, event.type_)
def on_middle_click(self): def on_middle_click(self):

View File

@ -609,8 +609,7 @@ class FileTransfersTooltip():
self.sid = sid self.sid = sid
return False, self.widget return False, self.widget
@staticmethod def _create_tooltip(self, file_props, _sid):
def _create_tooltip(file_props, sid):
ft_table = Gtk.Table(2, 1) ft_table = Gtk.Table(2, 1)
ft_table.set_property('column-spacing', 2) ft_table.set_property('column-spacing', 2)
current_row = 1 current_row = 1
@ -642,26 +641,7 @@ class FileTransfersTooltip():
if not transfered_len: if not transfered_len:
transfered_len = 0 transfered_len = 0
properties.append((_('Transferred: '), helpers.convert_bytes(transfered_len))) properties.append((_('Transferred: '), helpers.convert_bytes(transfered_len)))
status = '' status = self._get_current_status(file_props)
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')
properties.append((_('Status: '), status)) properties.append((_('Status: '), status))
file_desc = file_props.desc or '' file_desc = file_props.desc or ''
properties.append((_('Description: '), GLib.markup_escape_text( properties.append((_('Description: '), GLib.markup_escape_text(
@ -686,6 +666,24 @@ class FileTransfersTooltip():
ft_table.show_all() ft_table.show_all()
return ft_table 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): 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}"-pyopenssl \
mingw-w64-"${ARCH}"-"${PYTHON_ID}"-docutils \ mingw-w64-"${ARCH}"-"${PYTHON_ID}"-docutils \
mingw-w64-"${ARCH}"-"${PYTHON_ID}"-certifi \ 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="\ 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://dev.gajim.org/lovetox/pybonjour-python3.git
git+https://github.com/enthought/pywin32-ctypes.git git+https://github.com/enthought/pywin32-ctypes.git
keyring keyring