diff --git a/README.md b/README.md index 789bc7d0..bdb27607 100644 --- a/README.md +++ b/README.md @@ -1,24 +1,7 @@ -Dino -==== +![Dino](https://cdn.rawgit.com/fiaxh/3cb1391c5a94443098d004b4bf7c712c/raw/62f6a5e7de8402a0a89ffc73e8d1ed170054051c/dino-writing.svg) +======= -![screenshots](http://i.imgur.com/xIKPEFF.png) - -Install -------- -| OS | Package| -| ------------ | ------ | -| Arch Linux | [`dino-git`](https://aur.archlinux.org/packages/dino-git/) (AUR) | -| Fedora ≥ 25 | [`dino`](https://copr.fedorainfracloud.org/coprs/larma/dino/) (copr) | - -**Dependencies** - -* GLib (≥ 2.38) -* GTK (≥ 3.22) -* GPGME (For the OpenPGP plugin) -* libgee-0.8 (≥ 0.10) -* libgcrypt (For the OMEMO plugin) -* libnotify -* SQLite3 +![screenshots](https://i.imgur.com/xIKPEFF.png) Build ----- @@ -31,12 +14,26 @@ Build * ninja(-build) (recommend) * valac (≥ 0.30) +**Run-time dependencies** + +* GLib (≥ 2.38) +* GTK (≥ 3.22) +* GPGME (For the OpenPGP plugin) +* libgee-0.8 (≥ 0.10) +* libgcrypt (For the OMEMO plugin) +* libnotify +* SQLite3 + **Instructions** ./configure make build/dino +Resources +--------- +Please refer to [the wiki](https://github.com/dino/dino/wiki) for further information. + License ------- Dino - Modern Jabber/XMPP Client using GTK+/Vala diff --git a/libdino/src/service/conversation_manager.vala b/libdino/src/service/conversation_manager.vala index abc9e502..6e58ba8a 100644 --- a/libdino/src/service/conversation_manager.vala +++ b/libdino/src/service/conversation_manager.vala @@ -75,7 +75,7 @@ public class ConversationManager : StreamInteractionModule, Object { } public Gee.List get_active_conversations() { - ArrayList ret = new ArrayList(Conversation.equals_func); + Gee.List ret = new ArrayList(Conversation.equals_func); foreach (Account account in conversations.keys) { foreach (Conversation conversation in conversations[account].values) { if(conversation.active) ret.add(conversation); diff --git a/libdino/src/service/muc_manager.vala b/libdino/src/service/muc_manager.vala index 3c855c0e..17870b19 100644 --- a/libdino/src/service/muc_manager.vala +++ b/libdino/src/service/muc_manager.vala @@ -11,7 +11,7 @@ public class MucManager : StreamInteractionModule, Object { public signal void joined(Account account, Jid jid, string nick); public signal void left(Account account, Jid jid); public signal void subject_set(Account account, Jid jid, string? subject); - public signal void bookmarks_updated(Account account, ArrayList conferences); + public signal void bookmarks_updated(Account account, Gee.List conferences); private StreamInteractor stream_interactor; @@ -31,12 +31,18 @@ public class MucManager : StreamInteractionModule, Object { 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); stream.get_module(Xep.Muc.Module.IDENTITY).enter(stream, jid.bare_jid.to_string(), nick_, password); } public void part(Account account, Jid jid) { Core.XmppStream stream = stream_interactor.get_stream(account); - if (stream != null) stream.get_module(Xep.Muc.Module.IDENTITY).exit(stream, jid.bare_jid.to_string()); + if (stream == null) return; + unset_autojoin(stream, jid); + stream.get_module(Xep.Muc.Module.IDENTITY).exit(stream, jid.bare_jid.to_string()); + + Conversation? conversation = stream_interactor.get_module(ConversationManager.IDENTITY).get_conversation(jid, account); + if (conversation != null) stream_interactor.get_module(ConversationManager.IDENTITY).close_conversation(conversation); } public void change_subject(Account account, Jid jid, string subject) { @@ -156,7 +162,8 @@ public class MucManager : StreamInteractionModule, Object { stream_interactor.module_manager.get_module(account, Xep.Muc.Module.IDENTITY).subject_set.connect( (stream, subject, jid) => { subject_set(account, new Jid(jid), subject); }); - stream_interactor.module_manager.get_module(account, Xep.Bookmarks.Module.IDENTITY).conferences_updated.connect( (stream, conferences) => { + stream_interactor.module_manager.get_module(account, Xep.Bookmarks.Module.IDENTITY).received_conferences.connect( (stream, conferences) => { + sync_autojoin_active(account, conferences); bookmarks_updated(account, conferences); }); } @@ -195,6 +202,70 @@ public class MucManager : StreamInteractionModule, Object { } } } + + private void sync_autojoin_active(Account account, Gee.List conferences) { + Gee.List conversations = stream_interactor.get_module(ConversationManager.IDENTITY).get_active_conversations(); + leave_non_autojoin(account, conferences, conversations); + join_autojoin(account, conferences, conversations); + } + + private void leave_non_autojoin(Account account, Gee.List conferences, Gee.List conversations) { + foreach (Conversation conversation in conversations) { + if (conversation.type_ != Conversation.Type.GROUPCHAT || !conversation.account.equals(account)) continue; + bool is_autojoin = false; + foreach (Xep.Bookmarks.Conference conference in conferences) { + if (conference.jid == conversation.counterpart.to_string()) { + if (conference.autojoin) is_autojoin = true; + } + } + if (!is_autojoin) { + part(account, conversation.counterpart); + } + } + } + + private void join_autojoin(Account account, Gee.List conferences, Gee.List conversations) { + foreach (Xep.Bookmarks.Conference conference in conferences) { + if (!conference.autojoin) continue; + bool is_active = false; + foreach (Conversation conversation in conversations) { + if (conference.jid == conversation.counterpart.to_string()) is_active = true; + } + if (!is_active) { + join(account, new Jid(conference.jid), conference.nick, conference.password); + } + } + } + + private void set_autojoin(Core.XmppStream stream, Jid jid, string? nick, string? password) { + stream.get_module(Xep.Bookmarks.Module.IDENTITY).get_conferences(stream, (stream, conferences, storage) => { + Triple triple = storage as Triple; + Xep.Bookmarks.Conference changed = new Xep.Bookmarks.Conference(triple.a.to_string()) { nick=triple.b, password=triple.c, autojoin=true }; + foreach (Xep.Bookmarks.Conference conference in conferences) { + if (conference.jid == triple.a.bare_jid.to_string() && conference.nick == triple.b && conference.password == triple.c) { + if (!conference.autojoin) { + stream.get_module(Xep.Bookmarks.Module.IDENTITY).replace_conference(stream, conference, changed); + } + return; + } + } + stream.get_module(Xep.Bookmarks.Module.IDENTITY).add_conference(stream, changed); + }, Triple.create(jid, nick, password)); + } + + private void unset_autojoin(Core.XmppStream stream, Jid jid) { + stream.get_module(Xep.Bookmarks.Module.IDENTITY).get_conferences(stream, (stream, conferences, storage) => { + Jid jid_ = storage as Jid; + foreach (Xep.Bookmarks.Conference conference in conferences) { + if (conference.jid == jid_.bare_jid.to_string()) { + if (conference.autojoin) { + Xep.Bookmarks.Conference change = new Xep.Bookmarks.Conference(conference.jid) { nick=conference.nick, password=conference.password, autojoin=false }; + stream.get_module(Xep.Bookmarks.Module.IDENTITY).replace_conference(stream, conference, change); + } + } + } + }, jid); + } } } \ 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 c395dc3a..d09720d5 100644 --- a/main/src/ui/add_conversation/chat/roster_list.vala +++ b/main/src/ui/add_conversation/chat/roster_list.vala @@ -11,7 +11,7 @@ protected class RosterList : FilterableList { public signal void conversation_selected(Conversation? conversation); private StreamInteractor stream_interactor; - private HashMap rows = new HashMap(Jid.hash_func, Jid.equals_func); + private HashMap> rows = new HashMap>(Account.hash_func, Account.equals_func); public RosterList(StreamInteractor stream_interactor) { this.stream_interactor = stream_interactor; @@ -26,6 +26,7 @@ protected class RosterList : FilterableList { 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); } @@ -33,16 +34,16 @@ protected class RosterList : FilterableList { } private void on_removed_roster_item(Account account, Jid jid, Roster.Item roster_item) { - if (rows.has_key(jid)) { - remove(rows[jid]); - rows.unset(jid); + if (rows.has_key(account) && rows[account].has_key(jid)) { + remove(rows[account][jid]); + rows[account].unset(jid); } } 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); - rows[jid] = row; + rows[account][jid] = row; add(row); invalidate_sort(); invalidate_filter(); diff --git a/main/src/ui/add_conversation/conference/conference_list.vala b/main/src/ui/add_conversation/conference/conference_list.vala index d11271d8..ac74fa3a 100644 --- a/main/src/ui/add_conversation/conference/conference_list.vala +++ b/main/src/ui/add_conversation/conference/conference_list.vala @@ -11,7 +11,7 @@ protected class ConferenceList : FilterableList { public signal void conversation_selected(Conversation? conversation); private StreamInteractor stream_interactor; - private HashMap> lists = new HashMap>(); + private HashMap> lists = new HashMap>(); public ConferenceList(StreamInteractor stream_interactor) { this.stream_interactor = stream_interactor; @@ -42,7 +42,7 @@ protected class ConferenceList : FilterableList { } } - private static void on_conference_bookmarks_received(Core.XmppStream stream, ArrayList conferences, Object? o) { + private static void on_conference_bookmarks_received(Core.XmppStream stream, Gee.List conferences, Object? o) { Idle.add(() => { Tuple tuple = o as Tuple; ConferenceList list = tuple.a; @@ -89,13 +89,15 @@ internal class ConferenceListRow : ListRow { this.account = account; this.bookmark = bookmark; - if (bookmark.name != "" && bookmark.name != bookmark.jid) { - name_label.label = bookmark.name; + name_label.label = bookmark.name ?? bookmark.jid; + if (stream_interactor.get_accounts().size > 1) { + via_label.label = "via " + account.bare_jid.to_string(); + } else if (bookmark.name != null && bookmark.name != bookmark.jid) { via_label.label = bookmark.jid; } else { - name_label.label = bookmark.jid; via_label.visible = false; } + image.set_from_pixbuf((new AvatarGenerator(35, 35)).set_stateless(true).draw_jid(stream_interactor, jid, account)); } } diff --git a/main/src/ui/conversation_selector/conversation_row.vala b/main/src/ui/conversation_selector/conversation_row.vala index 2e7e321a..bb31b90d 100644 --- a/main/src/ui/conversation_selector/conversation_row.vala +++ b/main/src/ui/conversation_selector/conversation_row.vala @@ -12,7 +12,6 @@ namespace Dino.Ui.ConversationSelector { public abstract class ConversationRow : ListBoxRow { public signal void closed(); - public signal void disappeared(); [GtkChild] protected Image image; [GtkChild] private Label name_label; @@ -149,7 +148,6 @@ public abstract class ConversationRow : ListBoxRow { closed(); main_revealer.notify["child-revealed"].connect(() => { stream_interactor.get_module(ConversationManager.IDENTITY).close_conversation(conversation); - disappeared(); }); } diff --git a/main/src/ui/conversation_selector/list.vala b/main/src/ui/conversation_selector/list.vala index 3d6ee895..0e0ce554 100644 --- a/main/src/ui/conversation_selector/list.vala +++ b/main/src/ui/conversation_selector/list.vala @@ -23,19 +23,22 @@ public class List : ListBox { set_sort_func(sort); stream_interactor.get_module(ChatInteraction.IDENTITY).conversation_read.connect((conversation) => { - Idle.add(() => {if (rows.has_key(conversation)) rows[conversation].mark_read(); return false;}); + Idle.add(() => { if (rows.has_key(conversation)) rows[conversation].mark_read(); return false; }); }); stream_interactor.get_module(ChatInteraction.IDENTITY).conversation_unread.connect((conversation) => { - Idle.add(() => {if (rows.has_key(conversation)) rows[conversation].mark_unread(); return false;}); + Idle.add(() => { if (rows.has_key(conversation)) rows[conversation].mark_unread(); return false; }); }); stream_interactor.get_module(ConversationManager.IDENTITY).conversation_activated.connect((conversation) => { - Idle.add(() => {add_conversation(conversation); return false;}); + Idle.add(() => { add_conversation(conversation); return false; }); + }); + stream_interactor.get_module(ConversationManager.IDENTITY).conversation_deactivated.connect((conversation) => { + Idle.add(() => { remove_conversation(conversation); return false; }); }); stream_interactor.get_module(MessageProcessor.IDENTITY).message_received.connect((message, conversation) => { - Idle.add(() => {on_message_received(message, conversation); return false;}); + Idle.add(() => { on_message_received(message, conversation); return false; }); }); stream_interactor.get_module(MessageProcessor.IDENTITY).message_sent.connect((message, conversation) => { - Idle.add(() => {on_message_received(message, conversation); return false;}); + Idle.add(() => { on_message_received(message, conversation); return false; }); }); stream_interactor.get_module(PresenceManager.IDENTITY).show_received.connect((show, jid, account) => { Idle.add(() => { @@ -132,7 +135,6 @@ public class List : ListBox { rows[conversation] = row; add(row); row.closed.connect(() => { select_next_conversation(conversation); }); - row.disappeared.connect(() => { remove_conversation(conversation); }); row.main_revealer.set_reveal_child(true); } invalidate_sort(); @@ -156,6 +158,7 @@ public class List : ListBox { } private void remove_conversation(Conversation conversation) { + select_next_conversation(conversation); if (rows.has_key(conversation) && !conversation.active) { remove(rows[conversation]); rows.unset(conversation); diff --git a/main/src/ui/unified_window.vala b/main/src/ui/unified_window.vala index 1c4474c7..7fd4640a 100644 --- a/main/src/ui/unified_window.vala +++ b/main/src/ui/unified_window.vala @@ -39,8 +39,12 @@ public class UnifiedWindow : Window { stream_interactor.account_added.connect((account) => { check_stack(true); }); stream_interactor.account_removed.connect((account) => { check_stack(); }); - stream_interactor.get_module(ConversationManager.IDENTITY).conversation_activated.connect((conversation) => { check_stack(); }); - stream_interactor.get_module(ConversationManager.IDENTITY).conversation_deactivated.connect((conversation) => { check_stack(); }); + stream_interactor.get_module(ConversationManager.IDENTITY).conversation_activated.connect( (conversation) => { + Idle.add( () => { check_stack(); return false; }); + }); + stream_interactor.get_module(ConversationManager.IDENTITY).conversation_deactivated.connect( (conversation) => { + Idle.add( () => { check_stack(); return false; }); + }); accounts_placeholder.primary_button.clicked.connect(() => { get_application().activate_action("accounts", null); }); conversations_placeholder.primary_button.clicked.connect(() => { get_application().activate_action("add_chat", null); }); conversations_placeholder.secondary_button.clicked.connect(() => { get_application().activate_action("add_conference", null); }); diff --git a/xmpp-vala/src/module/xep/0048_bookmarks/conference.vala b/xmpp-vala/src/module/xep/0048_bookmarks/conference.vala index 21072c3f..6964f83d 100644 --- a/xmpp-vala/src/module/xep/0048_bookmarks/conference.vala +++ b/xmpp-vala/src/module/xep/0048_bookmarks/conference.vala @@ -28,7 +28,10 @@ public class Conference : Object { public string? name { get { return stanza_node.get_attribute(ATTRIBUTE_NAME); } - set { stanza_node.set_attribute(ATTRIBUTE_NAME, value); } + set { + if (value == null) return; // TODO actually remove + stanza_node.set_attribute(ATTRIBUTE_NAME, value); + } } public string? nick { @@ -58,6 +61,10 @@ public class Conference : Object { } set { StanzaNode? password_node = stanza_node.get_subnode(NODE_PASSWORD); + if (value == null) { + if (password_node != null) stanza_node.sub_nodes.remove(password_node); + return; + } if (password_node == null) { password_node = new StanzaNode.build(NODE_PASSWORD); stanza_node.put_node(password_node); diff --git a/xmpp-vala/src/module/xep/0048_bookmarks/module.vala b/xmpp-vala/src/module/xep/0048_bookmarks/module.vala index 0e0d9489..bf5bd612 100644 --- a/xmpp-vala/src/module/xep/0048_bookmarks/module.vala +++ b/xmpp-vala/src/module/xep/0048_bookmarks/module.vala @@ -8,25 +8,27 @@ private const string NS_URI = "storage:bookmarks"; public class Module : XmppStreamModule { public static ModuleIdentity IDENTITY = new ModuleIdentity(NS_URI, "0048_bookmarks_module"); - public signal void conferences_updated(XmppStream stream, ArrayList conferences); + public signal void received_conferences(XmppStream stream, Gee.List conferences); - [CCode (has_target = false)] public delegate void OnResult(XmppStream stream, ArrayList conferences, Object? reference); + [CCode (has_target = false)] public delegate void OnResult(XmppStream stream, Gee.List conferences, Object? storage); public void get_conferences(XmppStream stream, OnResult listener, Object? store) { StanzaNode get_node = new StanzaNode.build("storage", NS_URI).add_self_xmlns(); stream.get_module(PrivateXmlStorage.Module.IDENTITY).retrieve(stream, get_node, (stream, node, o) => { Tuple tuple = o as Tuple; OnResult on_result = tuple.a; - on_result(stream, get_conferences_from_stanza(node), tuple.b); + Gee.List conferences = get_conferences_from_stanza(node); + stream.get_module(Module.IDENTITY).received_conferences(stream, conferences); + on_result(stream, conferences, tuple.b); }, Tuple.create(listener, store)); } - public void set_conferences(XmppStream stream, ArrayList conferences) { + public void set_conferences(XmppStream stream, Gee.List conferences) { StanzaNode storage_node = (new StanzaNode.build("storage", NS_URI)).add_self_xmlns(); foreach (Conference conference in conferences) { storage_node.put_node(conference.stanza_node); } stream.get_module(PrivateXmlStorage.Module.IDENTITY).store(stream, storage_node, (stream, o) => { - stream.get_module(Module.IDENTITY).conferences_updated(stream, o as ArrayList); + stream.get_module(Module.IDENTITY).received_conferences(stream, o as ArrayList); }, conferences); }