Daniel Brötzmann pushed to branch mainwindow at gajim / gajim
Commits: e55803ca by wurstsalat at 2021-08-31T20:11:47+02:00 CallWidget: Ignore jingle file requests
- - - - - 6cafceaa by wurstsalat at 2021-08-31T20:14:36+02:00 Tooltips: Fix FileTransfersTooltip
- - - - - f31a04ca by wurstsalat at 2021-08-31T20:33:21+02:00 JingleSession: Don't rely on 'initiator' in session-initiate
- - - - - b5e487f4 by wurstsalat at 2021-09-02T22:38:00+02:00 Add ResourceSelector widget
- - - - - a1e80261 by wurstsalat at 2021-09-02T23:23:47+02:00 Interface: Adapt to Contact changes
- - - - - 2038a0d9 by wurstsalat at 2021-09-02T23:25:05+02:00 Jingle: Remove PM file transfers
- - - - - 57340dec by wurstsalat at 2021-09-02T23:26:05+02:00 Jingle File Transfer: Adapt to Contact changes
- - - - -
13 changed files:
- gajim/common/jingle_ft.py - gajim/common/jingle_session.py - gajim/common/modules/jingle.py - + gajim/data/gui/file_transfer_send.ui - − gajim/data/gui/filetransfers_send_file_dialog.ui - gajim/data/style/gajim.css - gajim/gtk/call_widget.py - gajim/gtk/controls/chat.py - + gajim/gtk/file_transfer_send.py - gajim/gtk/filetransfer.py - + gajim/gtk/resource_selector.py - gajim/gtk/tooltips.py - gajim/gui_interface.py
Changes:
===================================== gajim/common/jingle_ft.py ===================================== @@ -101,13 +101,6 @@ def __init__(self, session, transport=None, file_props=None, self.session = session self.media = 'file' self.nominated_cand = {} - if app.contacts.is_gc_contact(session.connection.name, - session.peerjid): - roomjid = session.peerjid.split('/')[0] - dstaddr = hashlib.sha1(('%s%s%s' % (self.file_props.sid, - session.ourjid, roomjid)) - .encode('utf-8')).hexdigest() - self.file_props.dstaddr = dstaddr self.state = State.NOT_STARTED self.states = { State.INITIALIZED : StateInitialized(self),
===================================== gajim/common/jingle_session.py ===================================== @@ -516,7 +516,17 @@ def __on_session_initiate(self, stanza, jingle, error, action): """ if self.state != JingleStates.ENDED: raise OutOfOrder - self.initiator = jingle['initiator'] + + # In "session-initiate", the <jingle/> element SHOULD possess an + # 'initiator' attribute. If it differs from the JID 'from', jingle + # 'initiator' SHOULD be ignored. + if jingle['initiator'] is None: + self.initiator = stanza['from'] + elif jingle['initiator'] != stanza['from']: + self.initiator = stanza['from'] + else: + self.initiator = jingle['initiator'] + self.responder = self.ourjid self.peerjid = self.initiator self.accepted = False # user did not accept this session yet
===================================== gajim/common/modules/jingle.py ===================================== @@ -207,16 +207,7 @@ def start_audio_video(self, jid):
def start_file_transfer(self, jid, file_props, request=False): logger.info("start file transfer with file: %s", file_props) - contact = app.contacts.get_contact_with_highest_priority( - self._account, app.get_jid_without_resource(jid)) - if app.contacts.is_gc_contact(self._account, jid): - gcc = jid.split('/') - if len(gcc) == 2: - contact = app.contacts.get_gc_contact(self._account, - gcc[0], - gcc[1]) - if contact is None: - return None + contact = self._con.get_module('Contacts').get_contact(jid) use_security = contact.supports(Namespace.JINGLE_XTLS) jingle = JingleSession(self._con, weinitiate=True,
===================================== gajim/data/gui/file_transfer_send.ui ===================================== @@ -0,0 +1,256 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- Generated with glade 3.38.2 --> +<interface> + <requires lib="gtk+" version="3.24"/> + <object class="GtkStack" id="send_stack"> + <property name="visible">True</property> + <property name="can-focus">False</property> + <property name="transition-type">slide-left-right</property> + <child> + <object class="GtkBox"> + <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="GtkLabel"> + <property name="visible">True</property> + <property name="can-focus">False</property> + <property name="label" translatable="yes">Files to send</property> + <property name="xalign">0</property> + <style> + <class name="dim-label"/> + </style> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">1</property> + </packing> + </child> + <child> + <object class="GtkBox"> + <property name="visible">True</property> + <property name="can-focus">False</property> + <property name="hexpand">True</property> + <property name="vexpand">True</property> + <property name="orientation">vertical</property> + <child> + <object class="GtkScrolledWindow"> + <property name="visible">True</property> + <property name="can-focus">True</property> + <property name="hscrollbar-policy">never</property> + <property name="shadow-type">in</property> + <child> + <object class="GtkViewport"> + <property name="visible">True</property> + <property name="can-focus">False</property> + <child> + <object class="GtkListBox" id="listbox"> + <property name="visible">True</property> + <property name="can-focus">False</property> + <property name="vexpand">True</property> + <property name="selection-mode">multiple</property> + <property name="activate-on-single-click">False</property> + <signal name="add" handler="_on_files_changed" swapped="no"/> + <signal name="remove" handler="_on_files_changed" swapped="no"/> + </object> + </child> + </object> + </child> + </object> + <packing> + <property name="expand">True</property> + <property name="fill">True</property> + <property name="position">0</property> + </packing> + </child> + <child> + <object class="GtkToolbar"> + <property name="visible">True</property> + <property name="can-focus">False</property> + <property name="show-arrow">False</property> + <property name="icon_size">1</property> + <child> + <object class="GtkToolButton"> + <property name="visible">True</property> + <property name="can-focus">False</property> + <property name="tooltip-text" translatable="yes">Add Files</property> + <property name="use-underline">True</property> + <property name="icon-name">list-add-symbolic</property> + <signal name="clicked" handler="_select_files" swapped="no"/> + </object> + <packing> + <property name="expand">False</property> + <property name="homogeneous">False</property> + </packing> + </child> + <child> + <object class="GtkToolButton"> + <property name="visible">True</property> + <property name="can-focus">False</property> + <property name="tooltip-text" translatable="yes">Remove Files</property> + <property name="use-underline">True</property> + <property name="icon-name">list-remove-symbolic</property> + <signal name="clicked" handler="_remove_files" swapped="no"/> + </object> + <packing> + <property name="expand">False</property> + <property name="homogeneous">True</property> + </packing> + </child> + <style> + <class name="inline-toolbar"/> + </style> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">1</property> + </packing> + </child> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">2</property> + </packing> + </child> + <child> + <object class="GtkLabel"> + <property name="visible">True</property> + <property name="can-focus">False</property> + <property name="label" translatable="yes">Description</property> + <property name="xalign">0</property> + <style> + <class name="dim-label"/> + </style> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">3</property> + </packing> + </child> + <child> + <object class="GtkScrolledWindow"> + <property name="height-request">70</property> + <property name="visible">True</property> + <property name="can-focus">True</property> + <property name="hscrollbar-policy">never</property> + <property name="shadow-type">in</property> + <child> + <object class="GtkTextView" id="description"> + <property name="visible">True</property> + <property name="can-focus">True</property> + <property name="wrap-mode">word-char</property> + <property name="left-margin">6</property> + <property name="right-margin">6</property> + <property name="top-margin">6</property> + <property name="bottom-margin">6</property> + </object> + </child> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">4</property> + </packing> + </child> + <child> + <object class="GtkButton" id="files_send"> + <property name="label" translatable="yes">_Send Files</property> + <property name="visible">True</property> + <property name="sensitive">False</property> + <property name="can-focus">True</property> + <property name="receives-default">True</property> + <property name="halign">center</property> + <property name="use-underline">True</property> + <signal name="clicked" handler="_on_send_clicked" swapped="no"/> + <style> + <class name="suggested-action"/> + </style> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">5</property> + </packing> + </child> + </object> + <packing> + <property name="name">file_selection</property> + </packing> + </child> + <child> + <object class="GtkBox" id="resource_box"> + <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="GtkLabel"> + <property name="visible">True</property> + <property name="can-focus">False</property> + <property name="label" translatable="yes">Choose Device</property> + <style> + <class name="large-header"/> + </style> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">0</property> + </packing> + </child> + <child> + <object class="GtkButton" id="resource_send"> + <property name="label" translatable="yes">_Send Files</property> + <property name="visible">True</property> + <property name="sensitive">False</property> + <property name="can-focus">True</property> + <property name="receives-default">False</property> + <property name="halign">center</property> + <property name="use-underline">True</property> + <signal name="clicked" handler="_on_send_to_resource" swapped="no"/> + <style> + <class name="suggested-action"/> + </style> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="pack-type">end</property> + <property name="position">1</property> + </packing> + </child> + <child> + <object class="GtkLabel" id="resource_instructions"> + <property name="visible">True</property> + <property name="can-focus">False</property> + <property name="justify">center</property> + <property name="wrap">True</property> + <property name="max-width-chars">50</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> + <placeholder/> + </child> + </object> + <packing> + <property name="name">resource_selection</property> + <property name="position">1</property> + </packing> + </child> + </object> +</interface>
===================================== gajim/data/gui/filetransfers_send_file_dialog.ui deleted ===================================== @@ -1,175 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<!-- Generated with glade 3.36.0 --> -<interface> - <requires lib="gtk+" version="3.22"/> - <object class="GtkGrid" id="send_file_grid"> - <property name="visible">True</property> - <property name="can_focus">False</property> - <property name="border_width">18</property> - <property name="row_spacing">6</property> - <property name="column_spacing">12</property> - <child> - <object class="GtkLabel"> - <property name="visible">True</property> - <property name="can_focus">False</property> - <property name="label" translatable="yes">Description</property> - <property name="xalign">0</property> - <style> - <class name="dim-label"/> - </style> - </object> - <packing> - <property name="left_attach">0</property> - <property name="top_attach">2</property> - <property name="width">2</property> - </packing> - </child> - <child> - <object class="GtkScrolledWindow"> - <property name="height_request">70</property> - <property name="visible">True</property> - <property name="can_focus">True</property> - <property name="hscrollbar_policy">never</property> - <property name="shadow_type">in</property> - <child> - <object class="GtkTextView" id="description"> - <property name="visible">True</property> - <property name="can_focus">True</property> - <property name="wrap_mode">word-char</property> - <property name="left_margin">6</property> - <property name="right_margin">6</property> - <property name="top_margin">6</property> - <property name="bottom_margin">6</property> - </object> - </child> - </object> - <packing> - <property name="left_attach">0</property> - <property name="top_attach">3</property> - <property name="width">2</property> - </packing> - </child> - <child> - <object class="GtkButton"> - <property name="label" translatable="yes">_Send Files</property> - <property name="visible">True</property> - <property name="can_focus">True</property> - <property name="receives_default">True</property> - <property name="tooltip_text" translatable="yes">Send</property> - <property name="halign">center</property> - <property name="use_underline">True</property> - <signal name="clicked" handler="_send" swapped="no"/> - <style> - <class name="suggested-action"/> - </style> - </object> - <packing> - <property name="left_attach">0</property> - <property name="top_attach">4</property> - <property name="width">2</property> - </packing> - </child> - <child> - <object class="GtkLabel"> - <property name="visible">True</property> - <property name="can_focus">False</property> - <property name="label" translatable="yes">Files to send</property> - <property name="xalign">0</property> - <style> - <class name="dim-label"/> - </style> - </object> - <packing> - <property name="left_attach">0</property> - <property name="top_attach">0</property> - <property name="width">2</property> - </packing> - </child> - <child> - <object class="GtkBox"> - <property name="visible">True</property> - <property name="can_focus">False</property> - <property name="hexpand">True</property> - <property name="vexpand">True</property> - <property name="orientation">vertical</property> - <child> - <object class="GtkScrolledWindow"> - <property name="visible">True</property> - <property name="can_focus">True</property> - <property name="hscrollbar_policy">never</property> - <property name="shadow_type">in</property> - <child> - <object class="GtkViewport"> - <property name="visible">True</property> - <property name="can_focus">False</property> - <child> - <object class="GtkListBox" id="listbox"> - <property name="visible">True</property> - <property name="can_focus">False</property> - <property name="vexpand">True</property> - <property name="selection_mode">multiple</property> - <property name="activate_on_single_click">False</property> - </object> - </child> - </object> - </child> - </object> - <packing> - <property name="expand">False</property> - <property name="fill">True</property> - <property name="position">0</property> - </packing> - </child> - <child> - <object class="GtkToolbar"> - <property name="visible">True</property> - <property name="can_focus">False</property> - <property name="show_arrow">False</property> - <property name="icon_size">1</property> - <child> - <object class="GtkToolButton"> - <property name="visible">True</property> - <property name="can_focus">False</property> - <property name="tooltip_text" translatable="yes">Add Files</property> - <property name="use_underline">True</property> - <property name="icon_name">list-add-symbolic</property> - <signal name="clicked" handler="_select_files" swapped="no"/> - </object> - <packing> - <property name="expand">False</property> - <property name="homogeneous">False</property> - </packing> - </child> - <child> - <object class="GtkToolButton"> - <property name="visible">True</property> - <property name="can_focus">False</property> - <property name="tooltip_text" translatable="yes">Remove Files</property> - <property name="use_underline">True</property> - <property name="icon_name">list-remove-symbolic</property> - <signal name="clicked" handler="_remove_files" swapped="no"/> - </object> - <packing> - <property name="expand">False</property> - <property name="homogeneous">True</property> - </packing> - </child> - <style> - <class name="inline-toolbar"/> - </style> - </object> - <packing> - <property name="expand">False</property> - <property name="fill">True</property> - <property name="position">1</property> - </packing> - </child> - </object> - <packing> - <property name="left_attach">0</property> - <property name="top_attach">1</property> - <property name="width">2</property> - </packing> - </child> - </object> -</interface>
===================================== gajim/data/style/gajim.css ===================================== @@ -426,6 +426,11 @@ .search-view-header label { color: @insensitive_fg_color; }
+.resource-selector list row { + padding: 6px; + border-bottom: 1px solid @borders; +} + /* Profile window */ #NicknameEntry:disabled { font-size: 28px;
===================================== gajim/gtk/call_widget.py ===================================== @@ -184,6 +184,9 @@ def _on_jingle_request(self, event): content_types = [] for item in event.contents: content_types.append(item.media) + if not any(item in ('audio', 'video') for item in content_types): + return + if 'audio' in content_types: self._set_jingle_state( 'audio',
===================================== gajim/gtk/controls/chat.py ===================================== @@ -245,7 +245,9 @@ def update_actions(self):
# Send file (Jingle) jingle_support = self.contact.supports(Namespace.JINGLE_FILE_TRANSFER_5) - jingle_conditions = jingle_support and self.contact.is_available + jingle_conditions = bool(jingle_support and + self.contact.is_available and + not self.contact.is_pm_contact) jingle = self._get_action('send-file-jingle-') jingle.set_enabled(online and jingle_conditions)
===================================== gajim/gtk/file_transfer_send.py ===================================== @@ -0,0 +1,134 @@ +# Copyright (C) 2003-2014 Yann Leboulanger <asterix AT lagaule.org> +# Copyright (C) 2005-2006 Dimitur Kirov <dkirov AT gmail.com> +# Copyright (C) 2005-2007 Nikos Kouremenos <kourem AT gmail.com> +# Copyright (C) 2006 Travis Shirk <travis AT pobox.com> +# +# This file is part of Gajim. +# +# Gajim is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published +# by the Free Software Foundation; version 3 only. +# +# Gajim is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Gajim. If not, see http://www.gnu.org/licenses/. + +from pathlib import Path + +from gi.repository import Gdk +from gi.repository import Gtk +from gi.repository import Pango + +from nbxmpp import Namespace + +from gajim.common import app +from gajim.common.i18n import _ + +from .filechoosers import FileChooserDialog +from .resource_selector import ResourceSelector +from .util import get_builder + + +class SendFileDialog(Gtk.ApplicationWindow): + def __init__(self, contact, send_callback, transient_for): + Gtk.ApplicationWindow.__init__(self) + self.set_application(app.app) + self.set_show_menubar(False) + self.set_resizable(True) + self.set_default_size(500, 350) + self.set_type_hint(Gdk.WindowTypeHint.DIALOG) + self.set_transient_for(transient_for) + self.set_title(_('Choose a File to Send…')) + self.set_destroy_with_parent(True) + + self._contact = contact + self._send_callback = send_callback + + self._ui = get_builder('file_transfer_send.ui') + self.add(self._ui.send_stack) + + self._ui.resource_instructions.set_text( + _('%s is online with multiple devices.\n' + 'Choose the device you would like to send the ' + 'file to.') % self._contact.name) + + self._resource_selector = ResourceSelector( + contact, + constraints=[Namespace.JINGLE_FILE_TRANSFER_5]) + self._resource_selector.connect( + 'selection-changed', self._on_resource_selection) + self._ui.resource_box.pack_start(self._resource_selector, 1, 0, 0) + + self.connect('key-press-event', self._key_press_event) + self._ui.connect_signals(self) + self.show_all() + + def _on_files_changed(self, _listbox, _row): + sensitive = bool(len(self._ui.listbox.get_children()) > 0) + self._ui.files_send.set_sensitive(sensitive) + + def _on_resource_selection(self, _selector, state): + self._ui.resource_send.set_sensitive(state) + + def _on_send_to_resource(self, _button): + resource_jid = self._resource_selector.get_jid() + self._send_files(resource_jid) + + def _on_send_clicked(self, _button): + count = len(self._contact.get_resources()) + if count == 0 or count > 1: + self._ui.send_stack.set_visible_child_name('resource_selection') + return + + resource_jid = self._contact.get_resources()[0].jid + self._send_files(resource_jid) + + def _send_files(self, resource_jid): + for file in self._ui.listbox.get_children(): + self._send_callback( + resource_jid, str(file.path), self._get_description()) + self.destroy() + + def _select_files(self, _button): + FileChooserDialog(self._set_files, + select_multiple=True, + transient_for=self, + path=app.settings.get('last_send_dir')) + + def _remove_files(self, _button): + selected = self._ui.listbox.get_selected_rows() + for item in selected: + self._ui.listbox.remove(item) + + def _set_files(self, file_names): + for file in file_names: + row = FileRow(file) + if row.path.is_dir(): + continue + last_dir = row.path.parent + self._ui.listbox.add(row) + self._ui.listbox.show_all() + app.settings.set('last_send_dir', str(last_dir)) + + def _get_description(self): + buffer_ = self._ui.description.get_buffer() + start, end = buffer_.get_bounds() + return buffer_.get_text(start, end, False) + + def _key_press_event(self, _widget, event): + if event.keyval == Gdk.KEY_Escape: + self.destroy() + + +class FileRow(Gtk.ListBoxRow): + def __init__(self, path): + Gtk.ListBoxRow.__init__(self) + self.path = Path(path) + label = Gtk.Label(label=self.path.name) + label.set_ellipsize(Pango.EllipsizeMode.END) + label.set_xalign(0) + self.add(label)
===================================== gajim/gtk/filetransfer.py ===================================== @@ -21,7 +21,6 @@ import time import logging from functools import partial -from pathlib import Path from enum import IntEnum, unique from datetime import datetime
@@ -30,6 +29,8 @@ from gi.repository import GLib from gi.repository import Pango
+from nbxmpp import JID + from gajim import gtkgui_helpers
from gajim.common import app @@ -47,7 +48,7 @@ from .dialogs import InformationDialog from .dialogs import ErrorDialog from .filechoosers import FileSaveDialog -from .filechoosers import FileChooserDialog +from .file_transfer_send import SendFileDialog from .tooltips import FileTransfersTooltip from .util import get_builder
@@ -228,10 +229,11 @@ def on_open(widget, file_props): sectext = _('File name: %s') % GLib.markup_escape_text(file_name) sectext += '\n' + _('Size: %s') % GLib.format_size_full( file_props.size, self.units) + client = app.get_client(file_props.tt_account) if file_props.type_ == 'r': - jid = file_props.sender.split('/')[0] - sender_name = app.contacts.get_first_contact_from_jid( - file_props.tt_account, jid).get_shown_name() + jid = JID.from_string(file_props.sender) + sender_name = client.get_module('Contacts').get_contact( + jid.bare).name sender = sender_name else: # You is a reply of who sent a file @@ -239,9 +241,9 @@ def on_open(widget, file_props): sectext += '\n' + _('Sender: %s') % sender sectext += '\n' + _('Recipient: ') if file_props.type_ == 's': - jid = file_props.receiver.split('/')[0] - receiver_name = app.contacts.get_first_contact_from_jid( - file_props.tt_account, jid).get_shown_name() + jid = JID.from_string(file_props.receiver) + receiver_name = client.get_module('Contacts').get_contact( + jid.bare).name recipient = receiver_name else: # You is a reply of who received a file @@ -250,7 +252,7 @@ def on_open(widget, file_props): if file_props.type_ == 'r': sectext += '\n' + _('Saved in: %s') % file_path
- dialog = HigDialog(app.interface.roster.window, + dialog = HigDialog(app.window, Gtk.MessageType.INFO, Gtk.ButtonsType.NONE, _('File transfer completed'), @@ -298,17 +300,10 @@ def show_stopped(self, jid, file_props, error_msg=''): self._ui.transfers_list.get_selection().unselect_all()
def show_hash_error(self, jid, file_props, account): - - def on_yes(dummy, fjid, file_props, account): + def _on_yes(): # Delete old file os.remove(file_props.file_name) - jid, resource = app.get_room_and_nick_from_fjid(fjid) - if resource: - contact = app.contacts.get_contact(account, jid, resource) - else: - contact = app.contacts.get_contact_with_highest_priority( - account, jid) - fjid = contact.get_full_jid() + # Request the file to the sender sid = helpers.get_random_string() new_file_props = FilesProp.getNewFileProp(account, sid) @@ -319,12 +314,15 @@ def on_yes(dummy, fjid, file_props, account): new_file_props.date = file_props.date new_file_props.hash_ = file_props.hash_ new_file_props.type_ = 'r' - tsid = app.connections[account].get_module( - 'Jingle').start_file_transfer(fjid, new_file_props, True) + tsid = client.get_module('Jingle').start_file_transfer( + jid, new_file_props, True)
new_file_props.transport_sid = tsid self.add_transfer(account, contact, new_file_props)
+ client = app.get_client(account) + contact = client.get_module('Contacts').get_contact(jid) + if file_props.type_ == 'r': file_name = os.path.basename(file_props.file_name) else: @@ -339,16 +337,14 @@ def on_yes(dummy, fjid, file_props, account): text=_('_No')), DialogButton.make('Accept', text=_('_Download Again'), - callback=on_yes, - args=[jid, - file_props, - account])]).show() + callback=_on_yes)]).show()
def show_file_send_request(self, account, contact): send_callback = partial(self.send_file, account, contact) - SendFileDialog(send_callback, self.window) + SendFileDialog(contact, send_callback, self.window)
- def send_file(self, account, contact, file_path, file_desc=''): + def send_file(self, account, contact, resource_jid, file_path, + file_desc=''): """ Start the real transfer(upload) of the file """ @@ -358,20 +354,15 @@ def send_file(self, account, contact, file_path, file_desc=''): ErrorDialog(pritext, sextext) return
- if isinstance(contact, str): - if contact.find('/') == -1: - return - (jid, resource) = contact.split('/', 1) - contact = app.contacts.create_contact(jid=jid, account=account, - resource=resource) file_name = os.path.split(file_path)[1] file_props = self.get_send_file_props(account, contact, file_path, file_name, file_desc) if file_props is None: return False
- app.connections[account].get_module('Jingle').start_file_transfer( - contact.get_full_jid(), file_props) + client = app.get_client(account) + client.get_module('Jingle').start_file_transfer( + str(resource_jid), file_props) self.add_transfer(account, contact, file_props)
return True @@ -383,8 +374,8 @@ def _start_receive(self, file_path, account, contact, file_props): file_props.file_name = file_path file_props.type_ = 'r' self.add_transfer(account, contact, file_props) - app.connections[account].get_module('Bytestream').send_file_approval( - file_props) + client = app.get_client(account) + client.get_module('Bytestream').send_file_approval(file_props)
def on_file_request_accepted(self, account, contact, file_props): def _on_accepted(account, contact, file_props, file_path): @@ -417,7 +408,7 @@ def _on_replace(): file_path, account, contact, file_props)
def _on_cancel(): - con.get_module('Bytestream').send_file_rejection( + client.get_module('Bytestream').send_file_rejection( file_props)
ConfirmationDialog( @@ -447,10 +438,10 @@ def _on_cancel(): self._start_receive(file_path, account, contact, file_props)
# Show file save as dialog - con = app.connections[account] + client = app.get_client(account) accept_cb = partial(_on_accepted, account, contact, file_props) - cancel_cb = partial(con.get_module('Bytestream').send_file_rejection, - file_props) + cancel_cb = partial( + client.get_module('Bytestream').send_file_rejection, file_props) FileSaveDialog(accept_cb, cancel_cb, path=app.settings.get('last_save_dir'), @@ -478,12 +469,12 @@ def _on_ok(): self.on_file_request_accepted(account, contact, file_props)
def _on_cancel(): - app.connections[account].get_module( + app.get_client(account).get_module( 'Bytestream').send_file_rejection(file_props)
ConfirmationDialog( _('File Transfer Request'), - _('%s wants to send you a file') % contact.get_shown_name(), + _('%s wants to send you a file') % contact.name, sectext, [DialogButton.make('Cancel', callback=_on_cancel), @@ -518,13 +509,13 @@ def set_status(self, file_props, status): text += received_size + '/' + full_size self.model.set(iter_, Column.PROGRESS, text)
- def pulse(): - p = self.model.get(iter_, Column.PULSE)[0] - if p == GLib.MAXINT32: + def _pulse(): + pulse_value = self.model.get(iter_, Column.PULSE)[0] + if pulse_value == GLib.MAXINT32: return False - self.model.set(iter_, Column.PULSE, p + 1) + self.model.set(iter_, Column.PULSE, pulse_value + 1) return True - GLib.timeout_add(100, pulse) + GLib.timeout_add(100, _pulse) elif status == 'hash_error': text = _('File error') + '\n' received_size = GLib.format_size_full( @@ -569,6 +560,9 @@ def _get_eta_and_speed(self, full_size, transfered_size, file_props): if not file_props.transfered_size: return 0., 0.
+ if file_props.elapsed_time == 0: + return 0., 0. + if len(file_props.transfered_size) == 1: speed = round(float(transfered_size) / file_props.elapsed_time) else: @@ -595,7 +589,7 @@ def _remove_transfer(self, iter_, sid, file_props): account = file_props.tt_account if account in app.connections: # there is a connection to the account - app.connections[account].get_module( + app.get_client(account).get_module( 'Bytestream').remove_transfer(file_props) if file_props.type_ == 'r': # we receive a file other = file_props.sender @@ -611,8 +605,7 @@ def _remove_transfer(self, iter_, sid, file_props): for event in app.events.get_events(account, jid, [ev_type]): if event.file_props.sid == file_props.sid: app.events.remove_events(account, jid, event) - app.interface.roster.draw_contact(jid, account) - app.interface.roster.show_title() + FilesProp.deleteFileProp(file_props) del file_props
@@ -678,8 +671,9 @@ def set_progress(self, typ, sid, transfered_size, iter_=None): self.model.set(iter_, 0, self.icons[status]) if transfered_size == full_size: # If we are receiver and this is a jingle session - if file_props.type_ == 'r' and \ - file_props.session_type == 'jingle' and file_props.hash_: + if (file_props.type_ == 'r' and + file_props.session_type == 'jingle' and + file_props.hash_): # Show that we are computing the hash self.set_status(file_props, 'computing') else: @@ -753,7 +747,7 @@ def add_transfer(self, account, contact, file_props): else: file_name = file_props.name text_props = GLib.markup_escape_text(file_name) + '\n' - text_props += contact.get_shown_name() + text_props += contact.name self.model.set(iter_, 1, text_labels, @@ -916,11 +910,11 @@ def _on_cancel_button_clicked(self, widget): account = file_props.tt_account if account not in app.connections: return - con = app.connections[account] + client = app.get_client(account) # Check if we are in a IBB transfer if file_props.direction: - con.get_module('IBB').send_close(file_props) - con.get_module('Bytestream').disconnect_transfer(file_props) + client.get_module('IBB').send_close(file_props) + client.get_module('Bytestream').disconnect_transfer(file_props) self.set_status(file_props, 'stop')
def _on_notify_ft_complete_toggled(self, widget, *args): @@ -1028,70 +1022,3 @@ def _on_remove_menuitem_activate(self, widget): def _on_file_transfers_window_key_press_event(self, widget, event): if event.keyval == Gdk.KEY_Escape: # ESCAPE self.window.hide() - - -class SendFileDialog(Gtk.ApplicationWindow): - def __init__(self, send_callback, transient_for): - Gtk.ApplicationWindow.__init__(self) - self.set_name('SendFileDialog') - self.set_application(app.app) - self.set_show_menubar(False) - self.set_resizable(True) - self.set_default_size(500, 350) - self.set_type_hint(Gdk.WindowTypeHint.DIALOG) - self.set_transient_for(transient_for) - self.set_title(_('Choose a File to Send…')) - self.set_destroy_with_parent(True) - - self._send_callback = send_callback - - self._ui = get_builder('filetransfers_send_file_dialog.ui') - self.add(self._ui.send_file_grid) - self.connect('key-press-event', self._key_press_event) - self._ui.connect_signals(self) - self.show_all() - - def _send(self, button): - for file in self._ui.listbox.get_children(): - self._send_callback(str(file.path), self._get_description()) - self.destroy() - - def _select_files(self, button): - FileChooserDialog(self._set_files, - select_multiple=True, - transient_for=self, - path=app.settings.get('last_send_dir')) - - def _remove_files(self, button): - selected = self._ui.listbox.get_selected_rows() - for item in selected: - self._ui.listbox.remove(item) - - def _set_files(self, filenames): - for file in filenames: - row = FileRow(file) - if row.path.is_dir(): - continue - last_dir = row.path.parent - self._ui.listbox.add(row) - self._ui.listbox.show_all() - app.settings.set('last_send_dir', str(last_dir)) - - def _get_description(self): - buffer_ = self._ui.description.get_buffer() - start, end = buffer_.get_bounds() - return buffer_.get_text(start, end, False) - - def _key_press_event(self, widget, event): - if event.keyval == Gdk.KEY_Escape: - self.destroy() - - -class FileRow(Gtk.ListBoxRow): - def __init__(self, path): - Gtk.ListBoxRow.__init__(self) - self.path = Path(path) - label = Gtk.Label(label=self.path.name) - label.set_ellipsize(Pango.EllipsizeMode.END) - label.set_xalign(0) - self.add(label)
===================================== gajim/gtk/resource_selector.py ===================================== @@ -0,0 +1,138 @@ +# This file is part of Gajim. +# +# Gajim is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published +# by the Free Software Foundation; version 3 only. +# +# Gajim is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Gajim. If not, see http://www.gnu.org/licenses/. + +import locale +import logging + +from gi.repository import GObject +from gi.repository import Gtk + +from gajim.common import app +from gajim.common.i18n import _ + +log = logging.getLogger('gajim.gui.resource_selector') + + +class ResourceSelector(Gtk.ScrolledWindow): + + __gsignals__ = { + 'selection-changed': ( + GObject.SignalFlags.RUN_LAST, + None, + (bool, )), + } + + def __init__(self, contact, constraints=None): + Gtk.ScrolledWindow.__init__(self) + self.set_shadow_type(Gtk.ShadowType.IN) + self.set_size_request(-1, 200) + self.get_style_context().add_class('resource-selector') + + self._listbox = Gtk.ListBox() + self._listbox.set_sort_func(self._sort_func) + self._listbox.connect('row-selected', self._on_row_selected) + self.add(self._listbox) + + self._contact = contact + self._contact.connect('presence-update', self._on_update) + self._contact.connect('caps-update', self._on_update) + + # Constraints include Namespaces a resource has to support + self._constraints = constraints or [] + + self._set_placeholder() + self._add_entries() + + self.show_all() + + @staticmethod + def _sort_func(row1, row2): + return locale.strcoll( + row1.device_text.lower(), row2.device_text.lower()) + + def _on_row_selected(self, _listbox, row): + state = bool(row is not None) + self.emit('selection-changed', state) + + def _set_placeholder(self): + image = Gtk.Image.new_from_icon_name( + 'dialog-warning-symbolic', Gtk.IconSize.DND) + label = Gtk.Label(label=_('No devices online')) + box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=6) + box.set_valign(Gtk.Align.CENTER) + box.get_style_context().add_class('dim-label') + box.add(image) + box.add(label) + box.show_all() + self._listbox.set_placeholder(box) + + def _add_entries(self): + for resource in self._contact.iter_resources(): + self._listbox.add(ResourceRow(resource, self._constraints)) + + def _on_update(self, _contact, _signal_name): + for child in self._listbox.get_children(): + self._listbox.remove(child) + self._add_entries() + + def get_jid(self): + return self._listbox.get_selected_row().jid + + +class ResourceRow(Gtk.ListBoxRow): + def __init__(self, resource_contact, constraints): + Gtk.ListBoxRow.__init__(self) + + self.jid = resource_contact.jid + + icon_name = 'computer-symbolic' + tooltip_text = _('Computer') + self.device_text = resource_contact.jid.resource + + disco_info = app.storage.cache.get_last_disco_info( + resource_contact.jid) + if disco_info is not None: + name, type_ = self._get_client_identity(disco_info) + if name is not None: + self.device_text = f'{name} ({resource_contact.jid.resource})' + if type_ is not None: + if type_ == 'phone': + icon_name = 'phone-symbolic' + tooltip_text = _('Phone') + + image = Gtk.Image() + image.set_from_icon_name(icon_name, Gtk.IconSize.DND) + image.set_tooltip_text(tooltip_text) + + name_label = Gtk.Label() + name_label.set_text(self.device_text) + + box = Gtk.Box(spacing=12) + box.add(image) + box.add(name_label) + + for constraint in constraints: + if not resource_contact.supports(constraint): + self.set_sensitive(False) + self.set_tooltip_text(_('This devices is not compatible.')) + + self.add(box) + self.show_all() + + @staticmethod + def _get_client_identity(disco_info): + for identity in disco_info.identities: + if identity.category == 'client': + return identity.name, identity.type + return None, None
===================================== gajim/gtk/tooltips.py ===================================== @@ -33,6 +33,8 @@ from gi.repository import GLib from gi.repository import Pango
+from nbxmpp import JID + from gajim.common import app from gajim.common import helpers from gajim.common.const import AvatarSize @@ -304,20 +306,19 @@ def _create_tooltip(self, file_props, _sid): file_name = file_props.name properties.append((_('File Name: '), GLib.markup_escape_text(file_name))) + + client = app.get_client(file_props.tt_account) if file_props.type_ == 'r': type_ = Q_('?Noun:Download') actor = _('Sender: ') - sender = file_props.sender.split('/')[0] - name = app.contacts.get_first_contact_from_jid( - file_props.tt_account, sender).get_shown_name() + sender = JID.from_string(file_props.sender) + name = client.get_module('Contacts').get_contact(sender.bare).name else: type_ = Q_('?Noun:Upload') actor = _('Recipient: ') - receiver = file_props.receiver - if hasattr(receiver, 'name'): - name = receiver.get_shown_name() - else: - name = receiver.split('/')[0] + receiver = JID.from_string(file_props.receiver) + name = client.get_module('Contacts').get_contact( + receiver.bare).name properties.append((Q_('?transfer type:Type: '), type_)) properties.append((actor, GLib.markup_escape_text(name)))
===================================== gajim/gui_interface.py ===================================== @@ -757,14 +757,10 @@ def handle_event_file_request_error(self, event): text=event.file_props.name)
def handle_event_file_request(self, event): - # TODO account = event.conn.name - if event.jid not in app.contacts.get_jid_list(account): - contact = app.contacts.create_not_in_roster_contact( - jid=event.jid, account=account) - app.contacts.add_contact(account, contact) - self.roster.add_contact(event.jid, account) - contact = app.contacts.get_first_contact_from_jid(account, event.jid) + client = app.get_client(account) + contact = client.get_module('Contacts').get_contact(event.jid) + if event.file_props.session_type == 'jingle': request = \ event.stanza.getTag('jingle').getTag('content').getTag( @@ -781,8 +777,7 @@ def handle_event_file_request(self, event): file_event = events.FileRequestEvent(event.file_props) self.add_event(account, event.jid, file_event) if helpers.allow_showing_notification(account): - txt = _('%s wants to send you a file.') % app.get_name_from_jid( - account, event.jid) + txt = _('%s wants to send you a file.') % contact.name event_type = _('File Transfer Request') app.notification.popup( event_type, @@ -823,11 +818,11 @@ def handle_event_file_rcv_completed(self, account, file_props): if file_props.error == 0: file_transfers.set_progress( file_props.type_, file_props.sid, file_props.received_len) - jid = app.get_jid_without_resource(str(file_props.receiver)) + jid = JID.from_string(file_props.receiver) app.nec.push_incoming_event( NetworkEvent('file-transfer-completed', file_props=file_props, - jid=jid)) + jid=jid.bare))
else: file_transfers.set_status(file_props, 'stop') @@ -947,56 +942,55 @@ def _popup_ft_result(self, account, jid, file_props): self.add_event(account, jid, event)
if file_props is not None: + client = app.get_client(account) + if file_props.type_ == 'r': - # Get the name of the sender, as it is in the roster - sender = file_props.sender.split('/')[0] - name = app.contacts.get_first_contact_from_jid( - account, sender).get_shown_name() + jid = JID.from_string(file_props.sender) + contact = client.get_module('contacts').get_contact(jid.bare) filename = os.path.basename(file_props.file_name)
if event_type == _('File Transfer Completed'): txt = _('%(filename)s received from %(name)s.') % { 'filename': filename, - 'name': name} + 'name': contact.name} icon_name = 'emblem-default' elif event_type == _('File Transfer Stopped'): txt = _('File transfer of %(filename)s from %(name)s ' 'stopped.') % { 'filename': filename, - 'name': name} + 'name': contact.name} icon_name = 'process-stop' else: # File transfer hash error txt = _('File transfer of %(filename)s from %(name)s ' 'failed.') % { 'filename': filename, - 'name': name} + 'name': contact.name} icon_name = 'process-stop' else: receiver = file_props.receiver if hasattr(receiver, 'jid'): receiver = receiver.jid - receiver = receiver.split('/')[0] - # Get the name of the contact, as it is in the roster - name = app.contacts.get_first_contact_from_jid( - account, receiver).get_shown_name() + + jid = JID.from_string(receiver) + contact = client.get_module('contacts').get_contact(jid.bare) filename = os.path.basename(file_props.file_name) if event_type == _('File Transfer Completed'): txt = _('You successfully sent %(filename)s to ' '%(name)s.') % { 'filename': filename, - 'name': name} + 'name': contact.name} icon_name = 'emblem-default' elif event_type == _('File Transfer Stopped'): txt = _('File transfer of %(filename)s to %(name)s ' 'stopped.') % { 'filename': filename, - 'name': name} + 'name': contact.name} icon_name = 'process-stop' else: # File transfer hash error txt = _('File transfer of %(filename)s to %(name)s ' 'failed.') % { 'filename': filename, - 'name': name} + 'name': contact.name} icon_name = 'process-stop' else: txt = ''
View it on GitLab: https://dev.gajim.org/gajim/gajim/-/compare/20f38c5069a7878340edc4964c4508c2...