diff --git a/libdino/src/service/chat_interaction.vala b/libdino/src/service/chat_interaction.vala index f13623ea..d7b8f839 100644 --- a/libdino/src/service/chat_interaction.vala +++ b/libdino/src/service/chat_interaction.vala @@ -124,26 +124,36 @@ public class ChatInteraction : StreamInteractionModule, Object { } private void on_message_received(Entities.Message message, Conversation conversation) { + send_delivery_receipt(conversation, message); if (is_active_focus(conversation)) { check_send_read(); conversation.read_up_to = message; send_chat_marker(conversation, message, Xep.ChatMarkers.MARKER_DISPLAYED); } else { + send_chat_marker(conversation, message, Xep.ChatMarkers.MARKER_RECEIVED); conversation_unread(conversation); } } private void send_chat_marker(Conversation conversation, Entities.Message message, string marker) { Core.XmppStream stream = stream_interactor.get_stream(conversation.account); - if (stream != null && Settings.instance().send_marker && + if (stream != null && + (marker == Xep.ChatMarkers.MARKER_RECEIVED || conversation.get_send_marker_setting() == Conversation.Setting.ON) && Xep.ChatMarkers.Module.requests_marking(message.stanza)) { stream.get_module(Xep.ChatMarkers.Module.IDENTITY).send_marker(stream, message.stanza.from, message.stanza_id, message.get_type_string(), marker); } } + private void send_delivery_receipt(Conversation conversation, Entities.Message message) { + Core.XmppStream stream = stream_interactor.get_stream(conversation.account); + if (stream != null && Xep.MessageDeliveryReceipts.Module.requests_receipt(message.stanza)) { + stream.get_module(Xep.MessageDeliveryReceipts.Module.IDENTITY).send_received(stream, message.from.to_string(), message.stanza_id); + } + } + private void send_chat_state_notification(Conversation conversation, string state) { Core.XmppStream stream = stream_interactor.get_stream(conversation.account); - if (stream != null && Settings.instance().send_typing && + if (stream != null && conversation.get_send_typing_setting() == Conversation.Setting.ON && conversation.type_ != Conversation.Type.GROUPCHAT) { stream.get_module(Xep.ChatStateNotifications.Module.IDENTITY).send_state(stream, conversation.counterpart.to_string(), state); } diff --git a/libdino/src/service/message_processor.vala b/libdino/src/service/message_processor.vala index e8fa3c03..ccaec915 100644 --- a/libdino/src/service/message_processor.vala +++ b/libdino/src/service/message_processor.vala @@ -72,7 +72,7 @@ public class MessageProcessor : StreamInteractionModule, Object { new_message.stanza_id = message.id; Jid from_jid = new Jid(message.from); if (!account.bare_jid.equals_bare(from_jid) || - stream_interactor.get_module(MucManager.IDENTITY).get_nick(from_jid.bare_jid, account) == from_jid.resourcepart) { + from_jid.equals(stream_interactor.get_module(MucManager.IDENTITY).get_own_jid(from_jid.bare_jid, account))) { new_message.direction = Entities.Message.DIRECTION_RECEIVED; } else { new_message.direction = Entities.Message.DIRECTION_SENT; diff --git a/libdino/src/service/muc_manager.vala b/libdino/src/service/muc_manager.vala index 71a66bb4..d6132d19 100644 --- a/libdino/src/service/muc_manager.vala +++ b/libdino/src/service/muc_manager.vala @@ -28,7 +28,7 @@ public class MucManager : StreamInteractionModule, Object { } public void join(Account account, Jid jid, string? nick, string? password) { - Core.XmppStream stream = stream_interactor.get_stream(account); + Core.XmppStream? stream = stream_interactor.get_stream(account); if (stream == null) return; string nick_ = nick ?? account.bare_jid.localpart ?? account.bare_jid.domainpart; set_autojoin(stream, jid, nick_, password); @@ -36,7 +36,7 @@ public class MucManager : StreamInteractionModule, Object { } public void part(Account account, Jid jid) { - Core.XmppStream stream = stream_interactor.get_stream(account); + Core.XmppStream? stream = stream_interactor.get_stream(account); if (stream == null) return; unset_autojoin(stream, jid); stream.get_module(Xep.Muc.Module.IDENTITY).exit(stream, jid.bare_jid.to_string()); @@ -47,7 +47,7 @@ public class MucManager : StreamInteractionModule, Object { [CCode (has_target = false)] public delegate void OnResult(Jid jid, Xep.DataForms.DataForm data_form, Object? store); public void get_config_form(Account account, Jid jid, OnResult on_result, Object? store) { - Core.XmppStream stream = stream_interactor.get_stream(account); + Core.XmppStream? stream = stream_interactor.get_stream(account); if (stream == null) return; stream.get_module(Xep.Muc.Module.IDENTITY).get_config_form(stream, jid.to_string(), (stream, jid, data_form, store) => { Tuple tuple = store as Tuple; @@ -56,20 +56,31 @@ public class MucManager : StreamInteractionModule, Object { } public void change_subject(Account account, Jid jid, string subject) { - Core.XmppStream stream = stream_interactor.get_stream(account); + Core.XmppStream? stream = stream_interactor.get_stream(account); if (stream != null) stream.get_module(Xep.Muc.Module.IDENTITY).change_subject(stream, jid.bare_jid.to_string(), subject); } public void change_nick(Account account, Jid jid, string new_nick) { - Core.XmppStream stream = stream_interactor.get_stream(account); + Core.XmppStream? stream = stream_interactor.get_stream(account); if (stream != null) stream.get_module(Xep.Muc.Module.IDENTITY).change_nick(stream, jid.bare_jid.to_string(), new_nick); } + public void invite(Account account, Jid muc, Jid invitee) { + Core.XmppStream? stream = stream_interactor.get_stream(account); + if (stream != null) stream.get_module(Xep.Muc.Module.IDENTITY).invite(stream, muc.bare_jid.to_string(), invitee.bare_jid.to_string()); + } + public void kick(Account account, Jid jid, string nick) { - Core.XmppStream stream = stream_interactor.get_stream(account); + Core.XmppStream? stream = stream_interactor.get_stream(account); if (stream != null) stream.get_module(Xep.Muc.Module.IDENTITY).kick(stream, jid.bare_jid.to_string(), nick); } + public bool kick_possible(Account account, Jid occupant) { + Core.XmppStream? stream = stream_interactor.get_stream(account); + if (stream != null) return stream.get_module(Xep.Muc.Module.IDENTITY).kick_possible(stream, occupant.to_string()); + return false; + } + public ArrayList? get_occupants(Jid jid, Account account) { if (is_groupchat(jid, account)) { return stream_interactor.get_module(PresenceManager.IDENTITY).get_full_jids(jid, account); @@ -79,9 +90,9 @@ public class MucManager : StreamInteractionModule, Object { public ArrayList? get_other_occupants(Jid jid, Account account) { ArrayList? occupants = get_occupants(jid, account); - string? nick = get_nick(jid, account); - if (occupants != null && nick != null) { - occupants.remove(new Jid(@"$(jid.bare_jid)/$nick")); + Jid? own_jid = get_own_jid(jid, account); + if (occupants != null && own_jid != null) { + occupants.remove(own_jid); } return occupants; } @@ -142,11 +153,15 @@ public class MucManager : StreamInteractionModule, Object { return null; } + public Xep.Muc.Role? get_role(Jid jid, Account account) { + Core.XmppStream? stream = stream_interactor.get_stream(account); + if (stream != null) return stream.get_flag(Xep.Muc.Flag.IDENTITY).get_occupant_role(jid.to_string()); + return null; + } + public Xep.Muc.Affiliation? get_affiliation(Jid muc_jid, Jid jid, Account account) { Core.XmppStream? stream = stream_interactor.get_stream(account); - if (stream != null) { - return stream.get_flag(Xep.Muc.Flag.IDENTITY).get_affiliation(muc_jid.to_string(), jid.to_string()); - } + if (stream != null) return stream.get_flag(Xep.Muc.Flag.IDENTITY).get_affiliation(muc_jid.to_string(), jid.to_string()); return null; } @@ -170,17 +185,19 @@ public class MucManager : StreamInteractionModule, Object { return null; } - public string? get_nick(Jid jid, Account account) { + public Jid? get_own_jid(Jid muc_jid, Account account) { Core.XmppStream? stream = stream_interactor.get_stream(account); if (stream != null) { Xep.Muc.Flag? flag = stream.get_flag(Xep.Muc.Flag.IDENTITY); - if (flag != null) return flag.get_muc_nick(jid.bare_jid.to_string()); + if (flag == null) return null; + string? nick = flag.get_muc_nick(muc_jid.bare_jid.to_string()); + if (nick != null) return new Jid.with_resource(muc_jid.bare_jid.to_string(), nick); } return null; } public bool is_joined(Jid jid, Account account) { - return get_nick(jid, account) != null; + return get_own_jid(jid, account) != null; } private void on_account_added(Account account) { diff --git a/main/src/ui/add_conversation/chat/dialog.vala b/main/src/ui/add_conversation/chat/dialog.vala index a36731a5..ee535f09 100644 --- a/main/src/ui/add_conversation/chat/dialog.vala +++ b/main/src/ui/add_conversation/chat/dialog.vala @@ -8,19 +8,21 @@ namespace Dino.Ui.AddConversation.Chat { public class Dialog : Gtk.Dialog { - public signal void conversation_opened(Conversation conversation); + public signal void selected(Account account, Jid jid); - private Button ok_button; + public Button ok_button; private RosterList roster_list; private SelectJidFragment select_jid_fragment; private StreamInteractor stream_interactor; + private Gee.List accounts; - public Dialog(StreamInteractor stream_interactor) { + public Dialog(StreamInteractor stream_interactor, Gee.List accounts) { Object(use_header_bar : 1); - this.title = _("Start Chat"); - this.modal = true; + modal = true; + this.stream_interactor = stream_interactor; + this.accounts = accounts; setup_headerbar(); setup_view(); @@ -37,19 +39,22 @@ public class Dialog : Gtk.Dialog { ok_button = new Button(); ok_button.get_style_context().add_class("suggested-action"); - ok_button.label = _("Start"); ok_button.sensitive = false; ok_button.visible = true; header_bar.pack_end(ok_button); cancel_button.clicked.connect(() => { close(); }); - ok_button.clicked.connect(on_ok_button_clicked); + ok_button.clicked.connect(() => { + ListRow? selected_row = roster_list.get_selected_row() as ListRow; + if (selected_row != null) selected(selected_row.account, selected_row.jid); + close(); + }); } private void setup_view() { - roster_list = new RosterList(stream_interactor); + roster_list = new RosterList(stream_interactor, accounts); roster_list.row_activated.connect(() => { ok_button.clicked(); }); - select_jid_fragment = new SelectJidFragment(stream_interactor, roster_list); + select_jid_fragment = new SelectJidFragment(stream_interactor, roster_list, accounts); select_jid_fragment.add_jid.connect((row) => { AddContactDialog add_contact_dialog = new AddContactDialog(stream_interactor); add_contact_dialog.set_transient_for(this); @@ -67,16 +72,6 @@ public class Dialog : Gtk.Dialog { }); get_content_area().add(select_jid_fragment); } - - protected void on_ok_button_clicked() { - ListRow? selected_row = roster_list.get_selected_row() as ListRow; - if (selected_row != null) { - Conversation conversation = stream_interactor.get_module(ConversationManager.IDENTITY).create_conversation(selected_row.jid, selected_row.account, Conversation.Type.CHAT); - stream_interactor.get_module(ConversationManager.IDENTITY).start_conversation(conversation, true); - conversation_opened(conversation); - } - close(); - } } } \ No newline at end of file diff --git a/main/src/ui/add_conversation/chat/roster_list.vala b/main/src/ui/add_conversation/chat/roster_list.vala index d09720d5..3c324cf6 100644 --- a/main/src/ui/add_conversation/chat/roster_list.vala +++ b/main/src/ui/add_conversation/chat/roster_list.vala @@ -10,27 +10,28 @@ protected class RosterList : FilterableList { public signal void conversation_selected(Conversation? conversation); private StreamInteractor stream_interactor; + private Gee.List accounts; private HashMap> rows = new HashMap>(Account.hash_func, Account.equals_func); - public RosterList(StreamInteractor stream_interactor) { + public RosterList(StreamInteractor stream_interactor, Gee.List accounts) { this.stream_interactor = stream_interactor; + this.accounts = accounts; set_filter_func(filter); set_header_func(header); set_sort_func(sort); stream_interactor.get_module(RosterManager.IDENTITY).removed_roster_item.connect( (account, jid, roster_item) => { - Idle.add(() => { on_removed_roster_item(account, jid, roster_item); return false;});}); + if (accounts.contains(account)) + Idle.add(() => { on_removed_roster_item(account, jid, roster_item); return false;}); + }); stream_interactor.get_module(RosterManager.IDENTITY).updated_roster_item.connect( (account, jid, roster_item) => { - Idle.add(() => { on_updated_roster_item(account, jid, roster_item); return false;});}); + if (accounts.contains(account)) + Idle.add(() => { on_updated_roster_item(account, jid, roster_item); return false;}); + }); - foreach (Account account in stream_interactor.get_accounts()) { - rows[account] = new HashMap(Jid.hash_func, Jid.equals_func); - foreach (Roster.Item roster_item in stream_interactor.get_module(RosterManager.IDENTITY).get_roster(account)) { - on_updated_roster_item(account, new Jid(roster_item.jid), roster_item); - } - } + foreach (Account a in accounts) fetch_roster_items(a); } private void on_removed_roster_item(Account account, Jid jid, Roster.Item roster_item) { @@ -42,13 +43,20 @@ protected class RosterList : FilterableList { private void on_updated_roster_item(Account account, Jid jid, Roster.Item roster_item) { on_removed_roster_item(account, jid, roster_item); - ListRow row = new ListRow.from_jid(stream_interactor, new Jid(roster_item.jid), account); + ListRow row = new ListRow.from_jid(stream_interactor, new Jid(roster_item.jid), account, accounts.size > 1); rows[account][jid] = row; add(row); invalidate_sort(); invalidate_filter(); } + private void fetch_roster_items(Account account) { + rows[account] = new HashMap(Jid.hash_func, Jid.equals_func); + foreach (Roster.Item roster_item in stream_interactor.get_module(RosterManager.IDENTITY).get_roster(account)) { + on_updated_roster_item(account, new Jid(roster_item.jid), roster_item); + } + } + private void header(ListBoxRow row, ListBoxRow? before_row) { if (row.get_header() == null && before_row != null) { row.set_header(new Separator(Orientation.HORIZONTAL)); diff --git a/main/src/ui/add_conversation/conference/dialog.vala b/main/src/ui/add_conversation/conference/dialog.vala index 412fb07c..9674cef2 100644 --- a/main/src/ui/add_conversation/conference/dialog.vala +++ b/main/src/ui/add_conversation/conference/dialog.vala @@ -88,7 +88,7 @@ public class Dialog : Gtk.Dialog { private void setup_jid_add_view() { conference_list = new ConferenceList(stream_interactor); conference_list.row_activated.connect(() => { ok_button.clicked(); }); - select_fragment = new SelectJidFragment(stream_interactor, conference_list); + select_fragment = new SelectJidFragment(stream_interactor, conference_list, stream_interactor.get_accounts()); select_fragment.add_jid.connect((row) => { AddGroupchatDialog dialog = new AddGroupchatDialog(stream_interactor); dialog.set_transient_for(this); diff --git a/main/src/ui/add_conversation/list_row.vala b/main/src/ui/add_conversation/list_row.vala index b53432a6..c68fde7b 100644 --- a/main/src/ui/add_conversation/list_row.vala +++ b/main/src/ui/add_conversation/list_row.vala @@ -17,12 +17,12 @@ public class ListRow : ListBoxRow { public ListRow() {} - public ListRow.from_jid(StreamInteractor stream_interactor, Jid jid, Account account) { + public ListRow.from_jid(StreamInteractor stream_interactor, Jid jid, Account account, bool show_account) { this.jid = jid; this.account = account; string display_name = Util.get_display_name(stream_interactor, jid, account); - if (stream_interactor.get_accounts().size > 1) { + if (show_account && stream_interactor.get_accounts().size > 1) { via_label.label = @"via $(account.bare_jid)"; this.has_tooltip = true; set_tooltip_text(jid.to_string()); diff --git a/main/src/ui/add_conversation/select_jid_fragment.vala b/main/src/ui/add_conversation/select_jid_fragment.vala index d0b214b5..71314235 100644 --- a/main/src/ui/add_conversation/select_jid_fragment.vala +++ b/main/src/ui/add_conversation/select_jid_fragment.vala @@ -23,13 +23,16 @@ public class SelectJidFragment : Gtk.Box { [GtkChild] private Button edit_button; [GtkChild] private Button remove_button; - private FilterableList filterable_list; - private ArrayList added_rows = new ArrayList(); private StreamInteractor stream_interactor; + private FilterableList filterable_list; + private Gee.List accounts; - public SelectJidFragment(StreamInteractor stream_interactor, FilterableList filterable_list) { + private ArrayList added_rows = new ArrayList(); + + public SelectJidFragment(StreamInteractor stream_interactor, FilterableList filterable_list, Gee.List accounts) { this.stream_interactor = stream_interactor; this.filterable_list = filterable_list; + this.accounts = accounts; filterable_list.visible = true; filterable_list.activate_on_single_click = false; @@ -57,7 +60,7 @@ public class SelectJidFragment : Gtk.Box { filterable_list.set_filter_values(values); Jid? parsed_jid = Jid.parse(str); if (parsed_jid != null && parsed_jid.localpart != null) { - foreach (Account account in stream_interactor.get_accounts()) { + foreach (Account account in accounts) { AddListRow row = new AddListRow(stream_interactor, str, account); filterable_list.add(row); added_rows.add(row); diff --git a/main/src/ui/chat_input/occupants_tab_completer.vala b/main/src/ui/chat_input/occupants_tab_completer.vala index 9ef73d8b..4d5d2904 100644 --- a/main/src/ui/chat_input/occupants_tab_completer.vala +++ b/main/src/ui/chat_input/occupants_tab_completer.vala @@ -90,8 +90,8 @@ class OccupantsTabCompletor { Gee.List messages = stream_interactor.get_module(MessageStorage.IDENTITY).get_messages(conversation, 10); for (int i = messages.size - 1; i > 0; i--) { string resourcepart = messages[i].from.resourcepart; - string own_nick = stream_interactor.get_module(MucManager.IDENTITY).get_nick(conversation.counterpart, conversation.account); - if (resourcepart != null && resourcepart != "" && resourcepart != own_nick && !ret.contains(resourcepart)) { + Jid? own_nick = stream_interactor.get_module(MucManager.IDENTITY).get_own_jid(conversation.counterpart, conversation.account); + if (resourcepart != null && resourcepart != "" && resourcepart != own_nick.resourcepart && !ret.contains(resourcepart)) { ret.add(resourcepart); } } diff --git a/main/src/ui/conversation_list_titlebar.vala b/main/src/ui/conversation_list_titlebar.vala index 327e1f50..e2d7fa26 100644 --- a/main/src/ui/conversation_list_titlebar.vala +++ b/main/src/ui/conversation_list_titlebar.vala @@ -22,9 +22,15 @@ public class ConversationListTitlebar : Gtk.HeaderBar { private void create_add_menu(Window window) { SimpleAction contacts_action = new SimpleAction("add_chat", null); contacts_action.activate.connect(() => { - AddConversation.Chat.Dialog add_chat_dialog = new AddConversation.Chat.Dialog(stream_interactor); + AddConversation.Chat.Dialog add_chat_dialog = new AddConversation.Chat.Dialog(stream_interactor, stream_interactor.get_accounts()); add_chat_dialog.set_transient_for(window); - add_chat_dialog.conversation_opened.connect((conversation) => conversation_opened(conversation)); + add_chat_dialog.title = _("Start Chat"); + add_chat_dialog.ok_button.label = _("Start"); + add_chat_dialog.selected.connect((account, jid) => { + Conversation conversation = stream_interactor.get_module(ConversationManager.IDENTITY).create_conversation(jid, account, Conversation.Type.CHAT); + stream_interactor.get_module(ConversationManager.IDENTITY).start_conversation(conversation, true); + conversation_opened(conversation); + }); add_chat_dialog.present(); }); GLib.Application.get_default().add_action(contacts_action); diff --git a/main/src/ui/conversation_selector/groupchat_pm_row.vala b/main/src/ui/conversation_selector/groupchat_pm_row.vala index f556b45d..c5cbcc09 100644 --- a/main/src/ui/conversation_selector/groupchat_pm_row.vala +++ b/main/src/ui/conversation_selector/groupchat_pm_row.vala @@ -44,7 +44,7 @@ public class GroupchatPmRow : ConversationRow { Box inner_box = builder.get_object("inner_box") as Box; Label jid_label = builder.get_object("jid_label") as Label; jid_label.label = conversation.counterpart.to_string(); - if (stream_interactor.get_module(MucManager.IDENTITY).get_nick(conversation.counterpart, conversation.account) != null) { + if (stream_interactor.get_module(MucManager.IDENTITY).is_joined(conversation.counterpart, conversation.account)) { inner_box.add(get_fulljid_box(conversation.counterpart)); } return main_box; diff --git a/main/src/ui/conversation_titlebar.vala b/main/src/ui/conversation_titlebar.vala index 0537a1b9..be532392 100644 --- a/main/src/ui/conversation_titlebar.vala +++ b/main/src/ui/conversation_titlebar.vala @@ -16,10 +16,12 @@ public class ConversationTitlebar : Gtk.HeaderBar { private Map encryption_radios = new HashMap(); private StreamInteractor stream_interactor; + private Window window; private Conversation? conversation; - public ConversationTitlebar(StreamInteractor stream_interactor) { + public ConversationTitlebar(StreamInteractor stream_interactor, Window window) { this.stream_interactor = stream_interactor; + this.window = window; create_conversation_menu(); create_encryption_menu(); @@ -67,7 +69,7 @@ public class ConversationTitlebar : Gtk.HeaderBar { groupchat_button.visible = conversation.type_ == Conversation.Type.GROUPCHAT; if (conversation.type_ == Conversation.Type.GROUPCHAT) { groupchat_button.set_use_popover(true); - OccupantMenu.View menu = new OccupantMenu.View(stream_interactor, conversation); + OccupantMenu.View menu = new OccupantMenu.View(stream_interactor, window, conversation); groupchat_button.set_popover(menu); } } diff --git a/main/src/ui/notifications.vala b/main/src/ui/notifications.vala index 32e8d7ad..725a3e26 100644 --- a/main/src/ui/notifications.vala +++ b/main/src/ui/notifications.vala @@ -96,8 +96,8 @@ public class Notifications : Object { private bool should_notify_message(Entities.Message message, Conversation conversation) { Conversation.NotifySetting notify = conversation.get_notification_setting(stream_interactor); if (notify == Conversation.NotifySetting.OFF) return false; - string? nick = stream_interactor.get_module(MucManager.IDENTITY).get_nick(conversation.counterpart, conversation.account); - if (notify == Conversation.NotifySetting.HIGHLIGHT && nick != null && !message.body.contains(nick)) return false; + Jid? nick = stream_interactor.get_module(MucManager.IDENTITY).get_own_jid(conversation.counterpart, conversation.account); + if (notify == Conversation.NotifySetting.HIGHLIGHT && nick != null && !message.body.contains(nick.resourcepart)) return false; return true; } } diff --git a/main/src/ui/occupant_menu/list.vala b/main/src/ui/occupant_menu/list.vala index ff30b180..50e6b300 100644 --- a/main/src/ui/occupant_menu/list.vala +++ b/main/src/ui/occupant_menu/list.vala @@ -35,7 +35,7 @@ public class List : Box { public void initialize_for_conversation(Conversation conversation) { this.conversation = conversation; - ArrayList? occupants = stream_interactor.get_module(MucManager.IDENTITY).get_occupants(conversation.counterpart, conversation.account); + Gee.List? occupants = stream_interactor.get_module(MucManager.IDENTITY).get_occupants(conversation.counterpart, conversation.account); if (occupants != null) { foreach (Jid occupant in occupants) { add_occupant(occupant); @@ -110,12 +110,24 @@ public class List : Box { default: aff_str = _("User"); break; } - Box box = new Box(Orientation.VERTICAL, 0) { visible=true }; - Label label = new Label("") { margin_left=10, margin_top=top?5:15, xalign=0, visible=true }; - label.set_markup(@"$(Markup.escape_text(aff_str))"); - box.add(label); - box.add(new Separator(Orientation.HORIZONTAL) { visible=true }); - return box; + + int count = 0; + foreach (ListRow row in rows.values) { + Xmpp.Xep.Muc.Affiliation aff = stream_interactor.get_module(MucManager.IDENTITY).get_affiliation(conversation.counterpart, row.jid, conversation.account); + if (aff == affiliation) count++; + } + + Label title_label = new Label("") { margin_left=10, xalign=0, visible=true }; + title_label.set_markup(@"$(Markup.escape_text(aff_str))"); + + Label count_label = new Label(@"$count") { xalign=0, margin_right=7, expand=true, visible=true }; + count_label.get_style_context().add_class("dim-label"); + + Grid grid = new Grid() { margin_top=top?5:15, column_spacing=5, hexpand=true, visible=true }; + grid.attach(title_label, 0, 0, 1, 1); + grid.attach(count_label, 1, 0, 1, 1); + grid.attach(new Separator(Orientation.HORIZONTAL) { expand=true, visible=true }, 0, 1, 2, 1); + return grid; } private bool filter(ListBoxRow r) { diff --git a/main/src/ui/occupant_menu/list_row.vala b/main/src/ui/occupant_menu/list_row.vala index b8b95758..f5776b4b 100644 --- a/main/src/ui/occupant_menu/list_row.vala +++ b/main/src/ui/occupant_menu/list_row.vala @@ -11,8 +11,8 @@ public class ListRow : ListBoxRow { [GtkChild] private Image image; [GtkChild] public Label name_label; - public Account account; - public Jid jid; + public Account? account; + public Jid? jid; public ListRow(StreamInteractor stream_interactor, Account account, Jid jid) { this.account = account; @@ -22,8 +22,9 @@ public class ListRow : ListBoxRow { Util.image_set_from_scaled_pixbuf(image, (new AvatarGenerator(30, 30, image.scale_factor)).draw_jid(stream_interactor, jid, account)); } - public void on_presence_received(Presence.Stanza presence) { - + public ListRow.label(string c, string text) { + name_label.label = text; + image.set_from_pixbuf((new AvatarGenerator(30, 30, 1)).set_greyscale(true).draw_text(c)); // why 1 } } diff --git a/main/src/ui/occupant_menu/view.vala b/main/src/ui/occupant_menu/view.vala index b6b25961..7918fe46 100644 --- a/main/src/ui/occupant_menu/view.vala +++ b/main/src/ui/occupant_menu/view.vala @@ -1,3 +1,4 @@ +using Gee; using Gtk; using Dino.Entities; @@ -10,22 +11,40 @@ public class View : Popover { private Stack stack = new Stack() { vhomogeneous=false, visible=true }; private List list; - private Label header_label = new Label("") { xalign=0.5f, hexpand=true, visible=true }; + private ListBox invite_list; - public View(StreamInteractor stream_interactor, Conversation conversation) { + public View(StreamInteractor stream_interactor, Window window, Conversation conversation) { this.stream_interactor = stream_interactor; this.conversation = conversation; + Box list_box = new Box(Orientation.VERTICAL, 1) { visible=true }; list = new List(stream_interactor, conversation) { visible=true }; - stack.add_named(list, "list"); - setup_menu(); + list_box.add(list); + + invite_list = new ListBox() { visible=true }; + invite_list.add(new ListRow.label("+", _("Invite")) {visible=true}); + list_box.add(invite_list); + invite_list.row_activated.connect((row) => { + hide(); + Gee.List acc_list = new ArrayList(Account.equals_func); + acc_list.add(conversation.account); + AddConversation.Chat.Dialog add_chat_dialog = new AddConversation.Chat.Dialog(stream_interactor, acc_list); + add_chat_dialog.set_transient_for(window); + add_chat_dialog.title = _("Invite to Conference"); + add_chat_dialog.ok_button.label = _("Invite"); + add_chat_dialog.selected.connect((account, jid) => { + stream_interactor.get_module(MucManager.IDENTITY).invite(conversation.account, conversation.counterpart, jid); + }); + add_chat_dialog.present(); + }); + + stack.add_named(list_box, "list"); add(stack); stack.visible_child_name = "list"; list.list_box.row_activated.connect((row) => { ListRow list_row = row as ListRow; - header_label.label = list_row.name_label.label; - show_menu(); + show_menu(list_row.jid, list_row.name_label.label); }); hide.connect(reset); @@ -35,25 +54,7 @@ public class View : Popover { stack.transition_type = StackTransitionType.NONE; stack.visible_child_name = "list"; list.list_box.unselect_all(); - } - - private void setup_menu() { - Box header_box = new Box(Orientation.HORIZONTAL, 5) { visible=true }; - header_box.add(new Image.from_icon_name("pan-start-symbolic", IconSize.SMALL_TOOLBAR) { visible=true }); - header_box.add(header_label); - - Button header_button = new Button() { relief=ReliefStyle.NONE, visible=true }; - header_button.add(header_box); - - ModelButton private_button = new ModelButton() { active=true, text=_("Start private conversation"), visible=true }; - - Box outer_box = new Box(Orientation.VERTICAL, 5) { margin=10, visible=true }; - outer_box.add(header_button); - outer_box.add(private_button); - stack.add_named(outer_box, "menu"); - - header_button.clicked.connect(show_list); - private_button.clicked.connect(private_conversation_button_clicked); + invite_list.unselect_all(); } private void show_list() { @@ -62,8 +63,34 @@ public class View : Popover { stack.visible_child_name = "list"; } - private void show_menu() { + private void show_menu(Jid jid, string name_label) { stack.transition_type = StackTransitionType.SLIDE_LEFT; + + Box header_box = new Box(Orientation.HORIZONTAL, 5) { visible=true }; + header_box.add(new Image.from_icon_name("pan-start-symbolic", IconSize.SMALL_TOOLBAR) { visible=true }); + header_box.add(new Label(name_label) { xalign=0.5f, hexpand=true, visible=true }); + + Button header_button = new Button() { relief=ReliefStyle.NONE, visible=true }; + header_button.add(header_box); + + Box outer_box = new Box(Orientation.VERTICAL, 5) { margin=10, visible=true }; + outer_box.add(header_button); + header_button.clicked.connect(show_list); + + ModelButton private_button = new ModelButton() { active=true, text=_("Start private conversation"), visible=true }; + outer_box.add(private_button); + private_button.clicked.connect(private_conversation_button_clicked); + + Jid? own_jid = stream_interactor.get_module(MucManager.IDENTITY).get_own_jid(conversation.counterpart, conversation.account); + Xmpp.Xep.Muc.Role? role = stream_interactor.get_module(MucManager.IDENTITY).get_role(own_jid, conversation.account); + + if (role == Xmpp.Xep.Muc.Role.MODERATOR && stream_interactor.get_module(MucManager.IDENTITY).kick_possible(conversation.account, jid)) { + ModelButton kick_button = new ModelButton() { active=true, text=_("Kick"), visible=true }; + outer_box.add(kick_button); + kick_button.clicked.connect(kick_button_clicked); + } + + stack.add_named(outer_box, "menu"); stack.visible_child_name = "menu"; } @@ -74,6 +101,13 @@ public class View : Popover { Conversation conversation = stream_interactor.get_module(ConversationManager.IDENTITY).create_conversation(list_row.jid, list_row.account, Conversation.Type.GROUPCHAT_PM); stream_interactor.get_module(ConversationManager.IDENTITY).start_conversation(conversation, true); } + + private void kick_button_clicked() { + ListRow? list_row = list.list_box.get_selected_row() as ListRow; + if (list_row == null) return; + + stream_interactor.get_module(MucManager.IDENTITY).kick(conversation.account, conversation.counterpart, list_row.jid.resourcepart); + } } } \ No newline at end of file diff --git a/main/src/ui/unified_window.vala b/main/src/ui/unified_window.vala index 670da9b2..c0bf0487 100644 --- a/main/src/ui/unified_window.vala +++ b/main/src/ui/unified_window.vala @@ -83,7 +83,7 @@ public class UnifiedWindow : Window { } private void setup_headerbar() { - conversation_titlebar = new ConversationTitlebar(stream_interactor) { visible=true }; + conversation_titlebar = new ConversationTitlebar(stream_interactor, this) { visible=true }; conversation_list_titlebar = new ConversationListTitlebar(stream_interactor, this) { visible=true }; headerbar_paned.add1(conversation_list_titlebar); headerbar_paned.add2(conversation_titlebar); diff --git a/xmpp-vala/src/module/iq/module.vala b/xmpp-vala/src/module/iq/module.vala index 909ec984..6cd474ca 100644 --- a/xmpp-vala/src/module/iq/module.vala +++ b/xmpp-vala/src/module/iq/module.vala @@ -11,7 +11,7 @@ namespace Xmpp.Iq { private HashMap responseListeners = new HashMap(); private HashMap> namespaceRegistrants = new HashMap>(); - [CCode (has_target = false)] public delegate void OnResult(XmppStream stream, Iq.Stanza iq, Object store); + [CCode (has_target = false)] public delegate void OnResult(XmppStream stream, Iq.Stanza iq, Object? store); public void send_iq(XmppStream stream, Iq.Stanza iq, OnResult? listener = null, Object? store = null) { try { stream.write(iq.stanza); diff --git a/xmpp-vala/src/module/xep/0045_muc/module.vala b/xmpp-vala/src/module/xep/0045_muc/module.vala index c098fd14..f649fdbe 100644 --- a/xmpp-vala/src/module/xep/0045_muc/module.vala +++ b/xmpp-vala/src/module/xep/0045_muc/module.vala @@ -103,10 +103,37 @@ public class Module : XmppStreamModule { stream.get_module(Presence.Module.IDENTITY).send_presence(stream, presence); } + public void invite(XmppStream stream, string to_muc, string jid) { + Message.Stanza message = new Message.Stanza(); + message.to = to_muc; + StanzaNode invite_node = new StanzaNode.build("x", NS_URI_USER).add_self_xmlns() + .put_node(new StanzaNode.build("invite", NS_URI_USER).put_attribute("to", jid)); + message.stanza.put_node(invite_node); + stream.get_module(Message.Module.IDENTITY).send_message(stream, message); + } + public void kick(XmppStream stream, string jid, string nick) { change_role(stream, jid, nick, "none"); } + /* XEP 0046: "A user cannot be kicked by a moderator with a lower affiliation." (XEP 0045 8.2) */ + public bool kick_possible(XmppStream stream, string occupant) { + string muc_jid = get_bare_jid(occupant); + Flag flag = stream.get_flag(Flag.IDENTITY); + string own_nick = flag.get_muc_nick(muc_jid); + Affiliation my_affiliation = flag.get_affiliation(muc_jid, muc_jid + "/" + own_nick); + Affiliation other_affiliation = flag.get_affiliation(muc_jid, occupant); + switch (my_affiliation) { + case Affiliation.MEMBER: + if (other_affiliation == Affiliation.ADMIN || other_affiliation == Affiliation.OWNER) return false; + break; + case Affiliation.ADMIN: + if (other_affiliation == Affiliation.OWNER) return false; + break; + } + return true; + } + [CCode (has_target = false)] public delegate void OnConfigFormResult(XmppStream stream, string jid, DataForms.DataForm data_form, Object? store); public void get_config_form(XmppStream stream, string jid, OnConfigFormResult listener, Object? store) { Iq.Stanza iq = new Iq.Stanza.get(new StanzaNode.build("query", NS_URI_OWNER).add_self_xmlns()) { to=jid }; diff --git a/xmpp-vala/src/module/xep/0049_private_xml_storage.vala b/xmpp-vala/src/module/xep/0049_private_xml_storage.vala index b843f63b..fc8f5b4c 100644 --- a/xmpp-vala/src/module/xep/0049_private_xml_storage.vala +++ b/xmpp-vala/src/module/xep/0049_private_xml_storage.vala @@ -35,12 +35,12 @@ namespace Xmpp.Xep.PrivateXmlStorage { public override string get_ns() { return NS_URI; } public override string get_id() { return IDENTITY.id; } - private static void on_store_response(XmppStream stream, Iq.Stanza iq, Object o) { + private static void on_store_response(XmppStream stream, Iq.Stanza iq, Object? o) { Tuple tuple = o as Tuple; tuple.a(stream, tuple.b); } - private static void on_retrieve_response(XmppStream stream, Iq.Stanza iq, Object o) { + private static void on_retrieve_response(XmppStream stream, Iq.Stanza iq, Object? o) { Tuple tuple = o as Tuple; tuple.a(stream, iq.stanza.get_subnode("query", NS_URI), tuple.b); } diff --git a/xmpp-vala/src/module/xep/0060_pubsub.vala b/xmpp-vala/src/module/xep/0060_pubsub.vala index 84b7fe9a..8ba66995 100644 --- a/xmpp-vala/src/module/xep/0060_pubsub.vala +++ b/xmpp-vala/src/module/xep/0060_pubsub.vala @@ -63,7 +63,7 @@ namespace Xmpp.Xep.Pubsub { } } - private static void on_received_request_response(XmppStream stream, Iq.Stanza iq, Object o) { + private static void on_received_request_response(XmppStream stream, Iq.Stanza iq, Object? o) { Tuple tuple = o as Tuple; OnResult on_result = tuple.a; StanzaNode event_node = iq.stanza.get_subnode("pubsub", NS_URI); diff --git a/xmpp-vala/src/module/xep/0184_message_delivery_receipts.vala b/xmpp-vala/src/module/xep/0184_message_delivery_receipts.vala index 80eea852..423bc3fe 100644 --- a/xmpp-vala/src/module/xep/0184_message_delivery_receipts.vala +++ b/xmpp-vala/src/module/xep/0184_message_delivery_receipts.vala @@ -8,6 +8,17 @@ namespace Xmpp.Xep.MessageDeliveryReceipts { public signal void receipt_received(XmppStream stream, string jid, string id); + public void send_received(XmppStream stream, string from, string message_id) { + Message.Stanza received_message = new Message.Stanza(); + received_message.to = from; + received_message.stanza.put_node(new StanzaNode.build("received", NS_URI).add_self_xmlns().put_attribute("id", message_id)); + stream.get_module(Message.Module.IDENTITY).send_message(stream, received_message); + } + + public static bool requests_receipt(Message.Stanza message) { + return message.stanza.get_subnode("request", NS_URI) != null; + } + public override void attach(XmppStream stream) { ServiceDiscovery.Module.require(stream); Message.Module.require(stream); @@ -33,18 +44,9 @@ namespace Xmpp.Xep.MessageDeliveryReceipts { StanzaNode? received_node = message.stanza.get_subnode("received", NS_URI); if (received_node != null) { receipt_received(stream, message.from, received_node.get_attribute("id", NS_URI)); - } else if (message.stanza.get_subnode("request", NS_URI) != null) { - send_received(stream, message); } } - private void send_received(XmppStream stream, Message.Stanza message) { - Message.Stanza received_message = new Message.Stanza(); - received_message.to = message.from; - received_message.stanza.put_node(new StanzaNode.build("received", NS_URI).add_self_xmlns().put_attribute("id", message.id)); - stream.get_module(Message.Module.IDENTITY).send_message(stream, received_message); - } - private void pre_send_message(XmppStream stream, Message.Stanza message) { StanzaNode? received_node = message.stanza.get_subnode("received", NS_URI); if (received_node != null) return; diff --git a/xmpp-vala/src/module/xep/0280_message_carbons.vala b/xmpp-vala/src/module/xep/0280_message_carbons.vala index 58805a96..318e4421 100644 --- a/xmpp-vala/src/module/xep/0280_message_carbons.vala +++ b/xmpp-vala/src/module/xep/0280_message_carbons.vala @@ -48,7 +48,6 @@ namespace Xmpp.Xep.MessageCarbons { if (forwarded_node != null) { StanzaNode? message_node = forwarded_node.get_subnode("message", Message.NS_URI); string? from_attribute = message_node.get_attribute("from", Message.NS_URI); - // The security model assumed by this document is that all of the resources for a single user are in the same trust boundary. // Any forwarded copies received by a Carbons-enabled client MUST be from that user's bare JID; any copies that do not meet this requirement MUST be ignored. if (from_attribute != null && from_attribute == get_bare_jid(stream.get_flag(Bind.Flag.IDENTITY).my_jid)) { if (received_node != null) { diff --git a/xmpp-vala/src/module/xep/0333_chat_markers.vala b/xmpp-vala/src/module/xep/0333_chat_markers.vala index e6bab2c6..11817a22 100644 --- a/xmpp-vala/src/module/xep/0333_chat_markers.vala +++ b/xmpp-vala/src/module/xep/0333_chat_markers.vala @@ -53,10 +53,6 @@ public class Module : XmppStreamModule { private void on_received_message(XmppStream stream, Message.Stanza message) { if (message.type_ != Message.Stanza.TYPE_CHAT) return; - if (requests_marking(message)) { - send_marker(stream, message.from, message.id, message.type_, MARKER_RECEIVED); - return; - } Gee.List nodes = message.stanza.get_all_subnodes(); foreach (StanzaNode node in nodes) { if (node.ns_uri == NS_URI && node.name in MARKERS) {