summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--data/ephraim.glade14
-rw-r--r--src/core/CMakeLists.txt1
-rw-r--r--src/core/ephraim.erl38
-rw-r--r--src/core/ephraim_conn.erl7
-rw-r--r--src/core/ephraim_conv.erl4
-rw-r--r--src/core/ephraim_event.erl42
-rw-r--r--src/core/ephraim_roster.erl52
-rw-r--r--src/gui/CMakeLists.txt1
-rw-r--r--src/gui/CellRendererAvatar.vala19
-rw-r--r--src/gui/CellRendererContact.vala17
-rw-r--r--src/gui/Contact.vala2
-rw-r--r--src/gui/CoreConnector.vala17
-rw-r--r--src/gui/Ephraim.vala24
-rw-r--r--src/gui/Roster.vala4
14 files changed, 207 insertions, 35 deletions
diff --git a/data/ephraim.glade b/data/ephraim.glade
index 629e2de..c4f2740 100644
--- a/data/ephraim.glade
+++ b/data/ephraim.glade
@@ -72,12 +72,20 @@
<object class="GtkHPaned" id="RosterPane">
<property name="visible">True</property>
<property name="can_focus">True</property>
- <property name="position">160</property>
+ <property name="position">200</property>
<child>
- <object class="GtkTreeView" id="Roster">
+ <object class="GtkScrolledWindow" id="ScrolledRoster">
<property name="visible">True</property>
<property name="can_focus">True</property>
- <property name="headers_visible">False</property>
+ <property name="hscrollbar_policy">never</property>
+ <property name="vscrollbar_policy">automatic</property>
+ <child>
+ <object class="GtkTreeView" id="Roster">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="headers_visible">False</property>
+ </object>
+ </child>
</object>
<packing>
<property name="resize">False</property>
diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt
index 792f279..c49763c 100644
--- a/src/core/CMakeLists.txt
+++ b/src/core/CMakeLists.txt
@@ -2,6 +2,7 @@ erl_target(ephraim-core
ephraim.erl
ephraim_conn.erl
ephraim_conv.erl
+ ephraim_event.erl
ephraim_roster.erl
ephraim_util.erl
OPTIONS
diff --git a/src/core/ephraim.erl b/src/core/ephraim.erl
index 523749c..bd36118 100644
--- a/src/core/ephraim.erl
+++ b/src/core/ephraim.erl
@@ -4,6 +4,7 @@
-record(state, {
conn :: pid(),
roster :: pid(),
+ event :: pid(),
convs :: dict(),
uis :: set()
}).
@@ -25,29 +26,35 @@ init() ->
Conn = spawn(ephraim_conn, init, []),
Roster = spawn(ephraim_roster, init, []),
- loop(#state{conn=Conn,roster=Roster,convs=dict:new(),uis=sets:new()}),
+ Event = spawn(ephraim_event, init, []),
+ loop(#state{conn=Conn,roster=Roster,event=Event,convs=dict:new(),uis=sets:new()}),
init:stop().
--spec get_conv(#state{},exmpp_jid:jid()) -> {#state{},pid()}.
+-spec get_conv(#state{},exmpp_jid:jid()) -> {#state{},pid()|undefined}.
get_conv(State, JID) ->
Key = exmpp_jid:to_lower(JID),
- case dict:find(Key, State#state.convs) of
- {ok, Conv} ->
- {State, Conv};
- error ->
- Conv = spawn(ephraim_conv, init, [Key]),
- Dict = dict:store(Key, Conv, State#state.convs),
- {State#state{convs=Dict}, Conv}
+ case Key of
+ {_,_,undefined} ->
+ {State, undefined};
+ _ ->
+ case dict:find(Key, State#state.convs) of
+ {ok, Conv} ->
+ {State, Conv};
+ error ->
+ Conv = spawn(ephraim_conv, init, [Key]),
+ Dict = dict:store(Key, Conv, State#state.convs),
+ {State#state{convs=Dict}, Conv}
+ end
end.
-spec loop(#state{}) -> ok.
loop(State) ->
receive
stop ->
- %dict:fold(fun(_,Conv,Msg) -> Conv ! Msg end, stop, State#state.convs),
ephraim_util:send_all_values(State#state.convs, stop),
State#state.conn ! stop,
State#state.roster ! stop,
+ State#state.event ! stop,
ok;
{register_ui, Pid} ->
@@ -78,9 +85,18 @@ loop(State) ->
{receive_message, From, Packet} ->
{State2, Conv} = get_conv(State, From),
- Conv ! {receive_message, Packet},
+ case Conv of
+ undefined ->
+ io:format("~p~n", [Packet]);
+ _ ->
+ Conv ! {receive_message, Packet}
+ end,
loop(State2);
+ {receive_event, From, Packet} ->
+ State#state.event ! {receive_event, From, Packet},
+ loop(State);
+
{receive_iq, IQ} ->
case IQ of
{iq, response, result, _, 'jabber:iq:roster', Payload, _, _, 'jabber:client'} ->
diff --git a/src/core/ephraim_conn.erl b/src/core/ephraim_conn.erl
index c4b163e..28e72b3 100644
--- a/src/core/ephraim_conn.erl
+++ b/src/core/ephraim_conn.erl
@@ -40,7 +40,12 @@ loop(State) ->
#received_packet{packet_type=message, raw_packet=Packet} ->
From = exmpp_xml:get_attribute(Packet, from, <<"unknown">>),
- ephraim ! {receive_message, From, Packet},
+ HasElement = exmpp_xml:has_element(Packet,'http://jabber.org/protocol/pubsub#event',event),
+ if HasElement ->
+ ephraim ! {receive_event, From, Packet};
+ true ->
+ ephraim ! {receive_message, From, Packet}
+ end,
loop(State);
#received_packet{packet_type=iq, raw_packet=Packet} ->
diff --git a/src/core/ephraim_conv.erl b/src/core/ephraim_conv.erl
index 8f53098..b502166 100644
--- a/src/core/ephraim_conv.erl
+++ b/src/core/ephraim_conv.erl
@@ -17,8 +17,8 @@ loop(State) ->
stop ->
ok;
- {receive_message, _Packet} ->
- %io:format("Received packet from ~p: ~p~n", [State#conv_state.jid, Packet]),
+ {receive_message, Packet} ->
+ io:format("Received packet from ~p:~n~p~n", [State#conv_state.jid, Packet]),
loop(State);
Msg ->
diff --git a/src/core/ephraim_event.erl b/src/core/ephraim_event.erl
new file mode 100644
index 0000000..dd86a7b
--- /dev/null
+++ b/src/core/ephraim_event.erl
@@ -0,0 +1,42 @@
+-module(ephraim_event).
+-compile([debug_info, export_all]).
+-include_lib("exmpp/include/exmpp.hrl").
+
+-spec init() -> ok.
+init() ->
+ loop().
+
+-spec handleEvent(binary(), #xmlel{}) -> ok.
+handleEvent(From, Item) ->
+ HasAvatarData = exmpp_xml:has_element(Item, 'urn:xmpp:avatar:data', data),
+ if HasAvatarData ->
+ Element = exmpp_xml:get_element(Item, 'urn:xmpp:avatar:data', data),
+ Data = base64:decode(exmpp_xml:get_cdata_from_list(Element#xmlel.children)),
+ ephraim ! {roster, {avatar, From, Data}};
+ true ->
+ io:format("Received unhandled event from ~p:~n~p~n", [From, Item])
+ end.
+
+-spec handleEvents(binary(), [#xmlel{}]) -> ok.
+handleEvents(_, []) ->
+ ok;
+handleEvents(From, [Item|Rest]) ->
+ handleEvent(From, Item),
+ handleEvents(From, Rest).
+
+-spec loop() -> ok.
+loop() ->
+ receive
+ stop ->
+ ok;
+
+ {receive_event, From, Packet} ->
+ Event = exmpp_xml:get_element(Packet, 'http://jabber.org/protocol/pubsub#event', event),
+ Items = exmpp_xml:get_element(Event, 'http://jabber.org/protocol/pubsub#event', items),
+ handleEvents(From, Items#xmlel.children),
+ loop();
+
+ Msg ->
+ io:format("ephraim_event: ~p~n", [Msg]),
+ loop()
+ end.
diff --git a/src/core/ephraim_roster.erl b/src/core/ephraim_roster.erl
index bfc40b1..2426761 100644
--- a/src/core/ephraim_roster.erl
+++ b/src/core/ephraim_roster.erl
@@ -6,11 +6,16 @@
entries :: dict()
}).
+-record(avatar, {
+ data :: binary()
+ }).
+
-record(roster_entry, {
name :: binary() | undefined,
subscription :: atom() | undefined,
groups :: set(),
- resources :: dict()
+ resources :: dict(),
+ avatar :: #avatar{} | undefined
}).
-record(resource_entry, {
@@ -19,7 +24,6 @@
status :: binary()
}).
-
-spec init() -> ok.
init() ->
loop(#roster{entries=dict:new()}).
@@ -45,13 +49,11 @@ updateResource(Roster, JID, Priority, Type, Show, Status) ->
_ ->
RosterEntry#roster_entry.resources
end,
- Entries = dict:store(BareJID, RosterEntry#roster_entry{resources=Resources}, Roster#roster.entries),
- if RosterEntry#roster_entry.subscription =:= undefined ->
- ok;
- true ->
- ephraim ! {ui_update, {roster_update, BareJID, RosterEntry#roster_entry.name, RosterEntry#roster_entry.subscription,
- sets:to_list(RosterEntry#roster_entry.groups), dict:to_list(Resources)}}
- end,
+ NewEntry = RosterEntry#roster_entry{resources=Resources},
+
+ Entries = dict:store(BareJID, NewEntry, Roster#roster.entries),
+ uiUpdate(BareJID, NewEntry),
+
Roster#roster{entries=Entries}.
-spec updateRosterEntry(#roster{}, binary(), binary(), atom(), set()) -> #roster{}.
@@ -63,10 +65,36 @@ updateRosterEntry(Roster, JID, Name, Subscription, Groups) ->
dict:new()
end,
NewEntry = #roster_entry{subscription=Subscription,name=Name,resources=Resources,groups=Groups},
+
Entries = dict:store(JID, NewEntry, Roster#roster.entries),
- ephraim ! {ui_update, {roster_update, JID, Name, Subscription, sets:to_list(Groups), dict:to_list(Resources)}},
+ uiUpdate(JID, NewEntry),
+
Roster#roster{entries=Entries}.
+-spec updateAvatar(#roster{}, binary(), binary()) -> #roster{}.
+updateAvatar(Roster, JID, Avatar) ->
+ RosterEntry = case dict:find(JID, Roster#roster.entries) of
+ {ok, Entry} ->
+ Entry;
+ error ->
+ #roster_entry{resources=dict:new()}
+ end,
+ NewEntry = RosterEntry#roster_entry{avatar=#avatar{data=Avatar}},
+ Entries = dict:store(JID, NewEntry, Roster#roster.entries),
+ uiUpdate(JID, NewEntry),
+ Roster#roster{entries=Entries}.
+
+-spec uiUpdate(binary(), #roster_entry{}) -> ok.
+uiUpdate(JID, RosterEntry) ->
+ if RosterEntry#roster_entry.subscription =:= undefined ->
+ ok;
+ true ->
+ ephraim ! {ui_update, {roster_update, JID, RosterEntry#roster_entry.name, RosterEntry#roster_entry.subscription,
+ sets:to_list(RosterEntry#roster_entry.groups), dict:to_list(RosterEntry#roster_entry.resources),
+ RosterEntry#roster_entry.avatar}},
+ ok
+ end.
+
-spec getGroups([#xmlel{}]) -> set().
getGroups(Els) ->
getGroups(sets:new(), Els).
@@ -116,6 +144,10 @@ loop(Roster) ->
Roster2 = updateResource(Roster, From, Priority, Type, Show, Status),
loop(Roster2);
+ {avatar, From, Avatar} ->
+ Roster2 = updateAvatar(Roster, From, Avatar),
+ loop(Roster2);
+
{roster_iq, Payload} ->
Roster2 = handleRosterIQs(Roster, Payload#xmlel.children),
%io:format("ephraim_roster: IQ: ~p~n", [Payload]),
diff --git a/src/gui/CMakeLists.txt b/src/gui/CMakeLists.txt
index def009b..6208e62 100644
--- a/src/gui/CMakeLists.txt
+++ b/src/gui/CMakeLists.txt
@@ -2,6 +2,7 @@ set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${ephraim_BINARY_DIR})
vala_precompile(VALA_C
"Ephraim.vala"
+ "CellRendererAvatar.vala"
"CellRendererContact.vala"
"CellRendererPresence.vala"
"Contact.vala"
diff --git a/src/gui/CellRendererAvatar.vala b/src/gui/CellRendererAvatar.vala
new file mode 100644
index 0000000..e4b53f4
--- /dev/null
+++ b/src/gui/CellRendererAvatar.vala
@@ -0,0 +1,19 @@
+public class CellRendererAvatar : Gtk.CellRendererPixbuf {
+ private Contact _contact;
+
+ public Contact contact {
+ get {
+ return _contact;
+ }
+ set {
+ _contact = value;
+
+ if(value == null) {
+ pixbuf = null;
+ return;
+ }
+
+ pixbuf = value.avatar;
+ }
+ }
+}
diff --git a/src/gui/CellRendererContact.vala b/src/gui/CellRendererContact.vala
index 7efe8cc..290ea09 100644
--- a/src/gui/CellRendererContact.vala
+++ b/src/gui/CellRendererContact.vala
@@ -8,7 +8,22 @@ public class CellRendererContact : Gtk.CellRendererText {
set {
_contact = value;
- text = contact.display_string;
+ string str = escape_markup(contact.display_string);
+
+ Gee.Map.Entry<string, Contact.Resource> res = contact.get_resource_with_highest_priority();
+ if(res != null && res.value.status != null) {
+ str += "\n<span size=\"small\" fgcolor=\"grey40\" style=\"italic\">" + escape_markup(res.value.status) + "</span>";
+ }
+
+ markup = str;
}
}
+
+ public CellRendererContact() {
+ ellipsize = Pango.EllipsizeMode.END;
+ }
+
+ private static string escape_markup(string str) {
+ return str.replace("&", "&amp;").replace("\"", "&quot;").replace("<", "&lt;").replace(">", "&gt;");
+ }
}
diff --git a/src/gui/Contact.vala b/src/gui/Contact.vala
index d79cfff..0e27b07 100644
--- a/src/gui/Contact.vala
+++ b/src/gui/Contact.vala
@@ -26,7 +26,9 @@ public class Contact : Object {
public string jid {get; construct;}
public string? name {get; construct;}
public Subscription subscription {get; set;}
+ public Gdk.Pixbuf? avatar {get; set; default = null;}
public Gee.TreeSet<string> groups = new Gee.TreeSet<string>();
+
public string display_string {get; private set;}
private Gee.HashMap<string, Resource> resources = new Gee.HashMap<string, Resource>();
diff --git a/src/gui/CoreConnector.vala b/src/gui/CoreConnector.vala
index 1030428..9c492b7 100644
--- a/src/gui/CoreConnector.vala
+++ b/src/gui/CoreConnector.vala
@@ -90,7 +90,7 @@ public class CoreConnector {
unowned Erl.Term term = store.term;
Erl.Term match_term;
- if((match_term = match("{roster_update,JID,Name,Subscription,Groups,Resources}", term)) != null) {
+ if((match_term = match("{roster_update,JID,Name,Subscription,Groups,Resources,Avatar}", term)) != null) {
Erl.Term jid_term = match_term.var_content("JID");
if(!jid_term.is_binary())
// TODO Debug output
@@ -151,6 +151,21 @@ public class CoreConnector {
resources = resources.tl();
}
+ Erl.Term avatar = match("{avatar,Data}", match_term.var_content("Avatar"));
+ if(avatar != null) {
+ Erl.Term avatarData = avatar.var_content("Data");
+
+ if(avatarData.is_binary()) {
+ InputStream avatarStream = new MemoryInputStream.from_data(avatarData.bin_ptr(), avatarData.bin_size(), null);
+ try {
+ contact.avatar = new Gdk.Pixbuf.from_stream_at_scale(avatarStream, 32, 32, true, null);
+ avatarStream.close(null);
+ } catch(Error e) {
+ // TODO Debug output
+ }
+ }
+ }
+
roster.update_contact(contact);
}
else {
diff --git a/src/gui/Ephraim.vala b/src/gui/Ephraim.vala
index 6474482..5130247 100644
--- a/src/gui/Ephraim.vala
+++ b/src/gui/Ephraim.vala
@@ -25,13 +25,14 @@ public class Ephraim {
rosterView.set_model(roster);
rosterView.query_tooltip.connect((x, y, keyboard_tip, tooltip) => {
+ Gtk.TreeModel model;
Gtk.TreeIter iter;
- if(!rosterView.get_tooltip_context(out x, out y, keyboard_tip, null, null, out iter))
+ if(!rosterView.get_tooltip_context(out x, out y, keyboard_tip, out model, null, out iter))
return false;
Value value;
- roster.get_value(iter, 0, out value);
+ model.get_value(iter, 0, out value);
Contact? c = value.get_object() as Contact;
if(c == null)
@@ -42,14 +43,27 @@ public class Ephraim {
if(r == null)
return false;
- tooltip.set_text("Resource: " + r.key);
+ tooltip.set_text("Resource: " + r.key + " (" + r.value.priority.to_string() + ")");
return true;
});
rosterView.has_tooltip = true;
- rosterView.append_column(new Gtk.TreeViewColumn.with_attributes("Presence", new CellRendererPresence(), "contact", 0, null));
- rosterView.append_column(new Gtk.TreeViewColumn.with_attributes("Contact", new CellRendererContact(), "contact", 0, null));
+ Gtk.TreeViewColumn presenceColumn = new Gtk.TreeViewColumn.with_attributes(null, new CellRendererPresence(), "contact", 0, null);
+ presenceColumn.min_width = 32;
+ rosterView.append_column(presenceColumn);
+
+ Gtk.TreeViewColumn contactColumn = new Gtk.TreeViewColumn();
+
+ Gtk.CellRenderer cellRendererContact = new CellRendererContact();
+ contactColumn.pack_start(cellRendererContact, true);
+ contactColumn.set_attributes(cellRendererContact, "contact", 0, null);
+
+ Gtk.CellRenderer cellRendererAvatar = new CellRendererAvatar();
+ contactColumn.pack_end(cellRendererAvatar, false);
+ contactColumn.set_attributes(cellRendererAvatar, "contact", 0, null);
+
+ rosterView.append_column(contactColumn);
window.visible = true;
diff --git a/src/gui/Roster.vala b/src/gui/Roster.vala
index bd6b663..f25584c 100644
--- a/src/gui/Roster.vala
+++ b/src/gui/Roster.vala
@@ -80,8 +80,10 @@ public class Roster : Object, Gtk.TreeModel {
}
public void get_value (Gtk.TreeIter iter, int column, out Value value) {
- if (column != 0 || iter.stamp != stamp || iter.user_data != this)
+ if (column != 0 || iter.stamp != stamp || iter.user_data != this) {
+ value = Value(Type.INVALID);
return;
+ }
Contact c = iter.user_data2 as Contact;
value = Value(typeof(Contact));