Compare commits
23 commits
Author | SHA1 | Date | |
---|---|---|---|
df0013e3f4 | |||
0521f50146 | |||
92b7d97339 | |||
7c048ecaef | |||
da383a2fe9 | |||
3a925c4c00 | |||
0ed0495e0a | |||
569d4141cf | |||
f44ded88c8 | |||
a74b894147 | |||
f011adac37 | |||
58379acc9f | |||
2fb8d29b34 | |||
7a27634732 | |||
d155ec15d2 | |||
baf96d9d9f | |||
179c766d19 | |||
Bohdan Horbeshko | 004824040d | ||
b6f9b54d76 | |||
1738bf8dc8 | |||
481a68fd89 | |||
89b9110fcb | |||
acf9c69470 |
2
.github/workflows/build.yml
vendored
2
.github/workflows/build.yml
vendored
|
@ -7,7 +7,7 @@ jobs:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v2
|
||||||
- run: sudo apt-get update
|
- run: sudo apt-get update
|
||||||
- run: sudo apt-get remove libunwind-14-dev
|
- run: sudo apt-get remove libunwind-14-dev
|
||||||
- run: sudo apt-get install -y build-essential gettext cmake valac libgee-0.8-dev libsqlite3-dev libgtk-4-dev libnotify-dev libgpgme-dev libsoup2.4-dev libgcrypt20-dev libqrencode-dev libgspell-1-dev libnice-dev libgstreamer1.0-dev libgstreamer-plugins-base1.0-dev libsrtp2-dev libwebrtc-audio-processing-dev libadwaita-1-dev
|
- run: sudo apt-get install -y build-essential gettext cmake valac libgee-0.8-dev libsqlite3-dev libgtk-4-dev libnotify-dev libgpgme-dev libsoup2.4-dev libgcrypt20-dev libqrencode-dev libnice-dev libgstreamer1.0-dev libgstreamer-plugins-base1.0-dev libsrtp2-dev libwebrtc-audio-processing-dev libadwaita-1-dev
|
||||||
- run: ./configure --with-tests --with-libsignal-in-tree
|
- run: ./configure --with-tests --with-libsignal-in-tree
|
||||||
- run: make
|
- run: make
|
||||||
- run: build/xmpp-vala-test
|
- run: build/xmpp-vala-test
|
||||||
|
|
|
@ -1,14 +0,0 @@
|
||||||
include(PkgConfigWithFallback)
|
|
||||||
find_pkg_config_with_fallback(Gspell
|
|
||||||
PKG_CONFIG_NAME gspell-1
|
|
||||||
LIB_NAMES gspell-1
|
|
||||||
INCLUDE_NAMES gspell.h
|
|
||||||
INCLUDE_DIR_SUFFIXES gspell-1 gspell-1/gspell
|
|
||||||
DEPENDS GTK3
|
|
||||||
)
|
|
||||||
|
|
||||||
include(FindPackageHandleStandardArgs)
|
|
||||||
find_package_handle_standard_args(Gspell
|
|
||||||
REQUIRED_VARS Gspell_LIBRARY
|
|
||||||
VERSION_VAR Gspell_VERSION)
|
|
||||||
|
|
|
@ -7,7 +7,7 @@ using Dino.Entities;
|
||||||
namespace Dino {
|
namespace Dino {
|
||||||
|
|
||||||
public class Database : Qlite.Database {
|
public class Database : Qlite.Database {
|
||||||
private const int VERSION = 25;
|
private const int VERSION = 26;
|
||||||
|
|
||||||
public class AccountTable : Table {
|
public class AccountTable : Table {
|
||||||
public Column<int> id = new Column.Integer("id") { primary_key = true, auto_increment = true };
|
public Column<int> id = new Column.Integer("id") { primary_key = true, auto_increment = true };
|
||||||
|
@ -93,6 +93,11 @@ public class Database : Qlite.Database {
|
||||||
|
|
||||||
// deduplication
|
// deduplication
|
||||||
index("message_account_counterpart_stanzaid_idx", {account_id, counterpart_id, stanza_id});
|
index("message_account_counterpart_stanzaid_idx", {account_id, counterpart_id, stanza_id});
|
||||||
|
index("message_account_counterpart_serverid_idx", {account_id, counterpart_id, server_id});
|
||||||
|
|
||||||
|
// message by marked
|
||||||
|
index("message_account_marked_idx", {account_id, marked});
|
||||||
|
|
||||||
fts({body});
|
fts({body});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -90,11 +90,9 @@ public class Dino.HistorySync {
|
||||||
if (!is_muc_mam && !from_our_server) return;
|
if (!is_muc_mam && !from_our_server) return;
|
||||||
|
|
||||||
// Get the server time of the message and store it in `mam_times`
|
// Get the server time of the message and store it in `mam_times`
|
||||||
Xmpp.MessageArchiveManagement.Flag? mam_flag = stream != null ? stream.get_flag(Xmpp.MessageArchiveManagement.Flag.IDENTITY) : null;
|
string? id = message.stanza.get_deep_attribute(Xmpp.MessageArchiveManagement.NS_URI + ":result", "id");
|
||||||
if (mam_flag == null) return;
|
|
||||||
string? id = message.stanza.get_deep_attribute(mam_flag.ns_ver + ":result", "id");
|
|
||||||
if (id == null) return;
|
if (id == null) return;
|
||||||
StanzaNode? delay_node = message.stanza.get_deep_subnode(mam_flag.ns_ver + ":result", StanzaForwarding.NS_URI + ":forwarded", DelayedDelivery.NS_URI + ":delay");
|
StanzaNode? delay_node = message.stanza.get_deep_subnode(Xmpp.MessageArchiveManagement.NS_URI + ":result", StanzaForwarding.NS_URI + ":forwarded", DelayedDelivery.NS_URI + ":delay");
|
||||||
if (delay_node == null) {
|
if (delay_node == null) {
|
||||||
warning("MAM result did not contain delayed time %s", message.stanza.to_string());
|
warning("MAM result did not contain delayed time %s", message.stanza.to_string());
|
||||||
return;
|
return;
|
||||||
|
@ -104,7 +102,7 @@ public class Dino.HistorySync {
|
||||||
mam_times[account][id] = time;
|
mam_times[account][id] = time;
|
||||||
|
|
||||||
// Check if this is the target message
|
// Check if this is the target message
|
||||||
string? query_id = message.stanza.get_deep_attribute(mam_flag.ns_ver + ":result", mam_flag.ns_ver + ":queryid");
|
string? query_id = message.stanza.get_deep_attribute(Xmpp.MessageArchiveManagement.NS_URI + ":result", Xmpp.MessageArchiveManagement.NS_URI + ":queryid");
|
||||||
if (query_id != null && id == catchup_until_id[account]) {
|
if (query_id != null && id == catchup_until_id[account]) {
|
||||||
debug("[%s] Hitted range (id) %s", account.bare_jid.to_string(), id);
|
debug("[%s] Hitted range (id) %s", account.bare_jid.to_string(), id);
|
||||||
hitted_range[query_id] = -2;
|
hitted_range[query_id] = -2;
|
||||||
|
@ -163,7 +161,7 @@ public class Dino.HistorySync {
|
||||||
if (current_row[db.mam_catchup.from_end]) return;
|
if (current_row[db.mam_catchup.from_end]) return;
|
||||||
|
|
||||||
debug("[%s] Fetching between ranges %s - %s", mam_server.to_string(), previous_row[db.mam_catchup.to_time].to_string(), current_row[db.mam_catchup.from_time].to_string());
|
debug("[%s] Fetching between ranges %s - %s", mam_server.to_string(), previous_row[db.mam_catchup.to_time].to_string(), current_row[db.mam_catchup.from_time].to_string());
|
||||||
current_row = yield fetch_between_ranges(account, mam_server, previous_row, current_row);
|
current_row = yield fetch_between_ranges(account, mam_server, previous_row, current_row, cancellable);
|
||||||
if (current_row == null) return;
|
if (current_row == null) return;
|
||||||
|
|
||||||
RowOption previous_row_opt = db.mam_catchup.select()
|
RowOption previous_row_opt = db.mam_catchup.select()
|
||||||
|
@ -214,13 +212,11 @@ public class Dino.HistorySync {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
// If we get PageResult.Duplicate, we still want to update the db row to the latest message.
|
|
||||||
|
|
||||||
// Catchup finished within first page. Update latest db entry.
|
// Catchup finished within first page. Update latest db entry.
|
||||||
if (latest_row_id != -1 &&
|
if (latest_row_id != -1 &&
|
||||||
page_result.page_result in new PageResult[] { PageResult.TargetReached, PageResult.NoMoreMessages, PageResult.Duplicate }) {
|
page_result.page_result in new PageResult[] { PageResult.TargetReached, PageResult.NoMoreMessages }) {
|
||||||
|
|
||||||
if (page_result.stanzas == null || page_result.stanzas.is_empty) return null;
|
if (page_result.stanzas == null) return null;
|
||||||
|
|
||||||
string latest_mam_id = page_result.query_result.last;
|
string latest_mam_id = page_result.query_result.last;
|
||||||
long latest_mam_time = (long) mam_times[account][latest_mam_id].to_unix();
|
long latest_mam_time = (long) mam_times[account][latest_mam_id].to_unix();
|
||||||
|
@ -272,7 +268,7 @@ public class Dino.HistorySync {
|
||||||
** Merges the `earlier_range` db row into the `later_range` db row.
|
** Merges the `earlier_range` db row into the `later_range` db row.
|
||||||
** @return The resulting range comprising `earlier_range`, `later_rage`, and everything in between. null if fetching/merge failed.
|
** @return The resulting range comprising `earlier_range`, `later_rage`, and everything in between. null if fetching/merge failed.
|
||||||
**/
|
**/
|
||||||
private async Row? fetch_between_ranges(Account account, Jid mam_server, Row earlier_range, Row later_range) {
|
private async Row? fetch_between_ranges(Account account, Jid mam_server, Row earlier_range, Row later_range, Cancellable? cancellable = null) {
|
||||||
int later_range_id = (int) later_range[db.mam_catchup.id];
|
int later_range_id = (int) later_range[db.mam_catchup.id];
|
||||||
DateTime earliest_time = new DateTime.from_unix_utc(earlier_range[db.mam_catchup.to_time]);
|
DateTime earliest_time = new DateTime.from_unix_utc(earlier_range[db.mam_catchup.to_time]);
|
||||||
DateTime latest_time = new DateTime.from_unix_utc(later_range[db.mam_catchup.from_time]);
|
DateTime latest_time = new DateTime.from_unix_utc(later_range[db.mam_catchup.from_time]);
|
||||||
|
@ -282,9 +278,9 @@ public class Dino.HistorySync {
|
||||||
earliest_time, earlier_range[db.mam_catchup.to_id],
|
earliest_time, earlier_range[db.mam_catchup.to_id],
|
||||||
latest_time, later_range[db.mam_catchup.from_id]);
|
latest_time, later_range[db.mam_catchup.from_id]);
|
||||||
|
|
||||||
PageRequestResult page_result = yield fetch_query(account, query_params, later_range_id);
|
PageRequestResult page_result = yield fetch_query(account, query_params, later_range_id, cancellable);
|
||||||
|
|
||||||
if (page_result.page_result == PageResult.TargetReached) {
|
if (page_result.page_result == PageResult.TargetReached || page_result.page_result == PageResult.NoMoreMessages) {
|
||||||
debug("[%s | %s] Merging range %i into %i", account.bare_jid.to_string(), mam_server.to_string(), earlier_range[db.mam_catchup.id], later_range_id);
|
debug("[%s | %s] Merging range %i into %i", account.bare_jid.to_string(), mam_server.to_string(), earlier_range[db.mam_catchup.id], later_range_id);
|
||||||
// Merge earlier range into later one.
|
// Merge earlier range into later one.
|
||||||
db.mam_catchup.update()
|
db.mam_catchup.update()
|
||||||
|
@ -330,9 +326,9 @@ public class Dino.HistorySync {
|
||||||
PageRequestResult? page_result = null;
|
PageRequestResult? page_result = null;
|
||||||
do {
|
do {
|
||||||
page_result = yield get_mam_page(account, query_params, page_result, cancellable);
|
page_result = yield get_mam_page(account, query_params, page_result, cancellable);
|
||||||
debug("Page result %s %b", page_result.page_result.to_string(), page_result.stanzas == null);
|
debug("[%s | %s] Page result %s (got stanzas: %s)", account.bare_jid.to_string(), query_params.mam_server.to_string(), page_result.page_result.to_string(), (page_result.stanzas != null).to_string());
|
||||||
|
|
||||||
if (page_result.page_result == PageResult.Error || page_result.page_result == PageResult.Cancelled || page_result.stanzas == null) return page_result;
|
if (page_result.page_result == PageResult.Error || page_result.page_result == PageResult.Cancelled || page_result.query_result.first == null) return page_result;
|
||||||
|
|
||||||
string earliest_mam_id = page_result.query_result.first;
|
string earliest_mam_id = page_result.query_result.first;
|
||||||
long earliest_mam_time = (long)mam_times[account][earliest_mam_id].to_unix();
|
long earliest_mam_time = (long)mam_times[account][earliest_mam_id].to_unix();
|
||||||
|
@ -357,7 +353,6 @@ public class Dino.HistorySync {
|
||||||
MorePagesAvailable,
|
MorePagesAvailable,
|
||||||
TargetReached,
|
TargetReached,
|
||||||
NoMoreMessages,
|
NoMoreMessages,
|
||||||
Duplicate,
|
|
||||||
Error,
|
Error,
|
||||||
Cancelled
|
Cancelled
|
||||||
}
|
}
|
||||||
|
@ -399,23 +394,25 @@ public class Dino.HistorySync {
|
||||||
string query_id = query_params.query_id;
|
string query_id = query_params.query_id;
|
||||||
string? after_id = query_params.start_id;
|
string? after_id = query_params.start_id;
|
||||||
|
|
||||||
|
var stanzas_for_query = stanzas.has_key(query_id) && !stanzas[query_id].is_empty ? stanzas[query_id] : null;
|
||||||
if (cancellable != null && cancellable.is_cancelled()) {
|
if (cancellable != null && cancellable.is_cancelled()) {
|
||||||
return new PageRequestResult(PageResult.Cancelled, query_result, stanzas[query_id]);
|
stanzas.unset(query_id);
|
||||||
|
return new PageRequestResult(PageResult.Cancelled, query_result, stanzas_for_query);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (stanzas.has_key(query_id) && !stanzas[query_id].is_empty) {
|
if (stanzas_for_query != null) {
|
||||||
|
|
||||||
// Check it we reached our target (from_id)
|
// Check it we reached our target (from_id)
|
||||||
foreach (Xmpp.MessageStanza message in stanzas[query_id]) {
|
foreach (Xmpp.MessageStanza message in stanzas_for_query) {
|
||||||
Xmpp.MessageArchiveManagement.MessageFlag? mam_message_flag = Xmpp.MessageArchiveManagement.MessageFlag.get_flag(message);
|
Xmpp.MessageArchiveManagement.MessageFlag? mam_message_flag = Xmpp.MessageArchiveManagement.MessageFlag.get_flag(message);
|
||||||
if (mam_message_flag != null && mam_message_flag.mam_id != null) {
|
if (mam_message_flag != null && mam_message_flag.mam_id != null) {
|
||||||
if (after_id != null && mam_message_flag.mam_id == after_id) {
|
if (after_id != null && mam_message_flag.mam_id == after_id) {
|
||||||
// Successfully fetched the whole range
|
// Successfully fetched the whole range
|
||||||
yield send_messages_back_into_pipeline(account, query_id, cancellable);
|
yield send_messages_back_into_pipeline(account, query_id, cancellable);
|
||||||
if (cancellable != null && cancellable.is_cancelled()) {
|
if (cancellable != null && cancellable.is_cancelled()) {
|
||||||
return new PageRequestResult(PageResult.Cancelled, query_result, stanzas[query_id]);
|
return new PageRequestResult(PageResult.Cancelled, query_result, stanzas_for_query);
|
||||||
}
|
}
|
||||||
return new PageRequestResult(PageResult.TargetReached, query_result, stanzas[query_id]);
|
return new PageRequestResult(PageResult.TargetReached, query_result, stanzas_for_query);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -423,37 +420,9 @@ public class Dino.HistorySync {
|
||||||
// Message got filtered out by xmpp-vala, but succesful range fetch nevertheless
|
// Message got filtered out by xmpp-vala, but succesful range fetch nevertheless
|
||||||
yield send_messages_back_into_pipeline(account, query_id);
|
yield send_messages_back_into_pipeline(account, query_id);
|
||||||
if (cancellable != null && cancellable.is_cancelled()) {
|
if (cancellable != null && cancellable.is_cancelled()) {
|
||||||
return new PageRequestResult(PageResult.Cancelled, query_result, stanzas[query_id]);
|
return new PageRequestResult(PageResult.Cancelled, query_result, stanzas_for_query);
|
||||||
}
|
}
|
||||||
return new PageRequestResult(PageResult.TargetReached, query_result, stanzas[query_id]);
|
return new PageRequestResult(PageResult.TargetReached, query_result, stanzas_for_query);
|
||||||
}
|
|
||||||
|
|
||||||
// Check for duplicates. Go through all messages and build a db query.
|
|
||||||
foreach (Xmpp.MessageStanza message in stanzas[query_id]) {
|
|
||||||
Xmpp.MessageArchiveManagement.MessageFlag? mam_message_flag = Xmpp.MessageArchiveManagement.MessageFlag.get_flag(message);
|
|
||||||
if (mam_message_flag != null && mam_message_flag.mam_id != null) {
|
|
||||||
if (selection == null) {
|
|
||||||
selection = @"$(db.message.server_id) = ?";
|
|
||||||
} else {
|
|
||||||
selection += @" OR $(db.message.server_id) = ?";
|
|
||||||
}
|
|
||||||
selection_args += mam_message_flag.mam_id;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
var duplicates_qry = db.message.select()
|
|
||||||
.with(db.message.account_id, "=", account.id)
|
|
||||||
.where(selection, selection_args);
|
|
||||||
// We don't want messages from different MAM servers to interfere with each other.
|
|
||||||
if (!query_params.mam_server.equals_bare(account.bare_jid)) {
|
|
||||||
duplicates_qry.with(db.message.counterpart_id, "=", db.get_jid_id(query_params.mam_server));
|
|
||||||
} else {
|
|
||||||
duplicates_qry.with(db.message.type_, "=", Message.Type.CHAT);
|
|
||||||
}
|
|
||||||
var duplicates_count = duplicates_qry.count();
|
|
||||||
if (duplicates_count > 0) {
|
|
||||||
// We got a duplicate although we thought we have to catch up.
|
|
||||||
// There was a server bug where prosody would send all messages if it didn't know the after ID that was given
|
|
||||||
page_result = PageResult.Duplicate;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -461,7 +430,7 @@ public class Dino.HistorySync {
|
||||||
if (cancellable != null && cancellable.is_cancelled()) {
|
if (cancellable != null && cancellable.is_cancelled()) {
|
||||||
page_result = PageResult.Cancelled;
|
page_result = PageResult.Cancelled;
|
||||||
}
|
}
|
||||||
return new PageRequestResult(page_result, query_result, stanzas.has_key(query_id) ? stanzas[query_id] : null);
|
return new PageRequestResult(page_result, query_result, stanzas_for_query);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async void send_messages_back_into_pipeline(Account account, string query_id, Cancellable? cancellable = null) {
|
private async void send_messages_back_into_pipeline(Account account, string query_id, Cancellable? cancellable = null) {
|
||||||
|
|
|
@ -170,19 +170,21 @@ public class MessageProcessor : StreamInteractionModule, Object {
|
||||||
|
|
||||||
XmppStream? stream = stream_interactor.get_stream(account);
|
XmppStream? stream = stream_interactor.get_stream(account);
|
||||||
Xmpp.MessageArchiveManagement.MessageFlag? mam_message_flag = Xmpp.MessageArchiveManagement.MessageFlag.get_flag(message);
|
Xmpp.MessageArchiveManagement.MessageFlag? mam_message_flag = Xmpp.MessageArchiveManagement.MessageFlag.get_flag(message);
|
||||||
Xmpp.MessageArchiveManagement.Flag? mam_flag = stream != null ? stream.get_flag(Xmpp.MessageArchiveManagement.Flag.IDENTITY) : null;
|
|
||||||
EntityInfo entity_info = stream_interactor.get_module(EntityInfo.IDENTITY);
|
EntityInfo entity_info = stream_interactor.get_module(EntityInfo.IDENTITY);
|
||||||
if (mam_message_flag != null && mam_flag != null && mam_flag.ns_ver == Xmpp.MessageArchiveManagement.NS_URI_2 && mam_message_flag.mam_id != null) {
|
if (mam_message_flag != null && mam_message_flag.mam_id != null) {
|
||||||
|
bool server_does_mam = entity_info.has_feature_cached(account, account.bare_jid, Xmpp.MessageArchiveManagement.NS_URI);
|
||||||
|
if (server_does_mam) {
|
||||||
new_message.server_id = mam_message_flag.mam_id;
|
new_message.server_id = mam_message_flag.mam_id;
|
||||||
|
}
|
||||||
} else if (message.type_ == Xmpp.MessageStanza.TYPE_GROUPCHAT) {
|
} else if (message.type_ == Xmpp.MessageStanza.TYPE_GROUPCHAT) {
|
||||||
bool server_supports_sid = (yield entity_info.has_feature(account, new_message.counterpart.bare_jid, Xep.UniqueStableStanzaIDs.NS_URI)) ||
|
bool server_supports_sid = (yield entity_info.has_feature(account, new_message.counterpart.bare_jid, Xep.UniqueStableStanzaIDs.NS_URI)) ||
|
||||||
(yield entity_info.has_feature(account, new_message.counterpart.bare_jid, Xmpp.MessageArchiveManagement.NS_URI_2));
|
(yield entity_info.has_feature(account, new_message.counterpart.bare_jid, Xmpp.MessageArchiveManagement.NS_URI));
|
||||||
if (server_supports_sid) {
|
if (server_supports_sid) {
|
||||||
new_message.server_id = Xep.UniqueStableStanzaIDs.get_stanza_id(message, new_message.counterpart.bare_jid);
|
new_message.server_id = Xep.UniqueStableStanzaIDs.get_stanza_id(message, new_message.counterpart.bare_jid);
|
||||||
}
|
}
|
||||||
} else if (message.type_ == Xmpp.MessageStanza.TYPE_CHAT) {
|
} else if (message.type_ == Xmpp.MessageStanza.TYPE_CHAT) {
|
||||||
bool server_supports_sid = (yield entity_info.has_feature(account, account.bare_jid, Xep.UniqueStableStanzaIDs.NS_URI)) ||
|
bool server_supports_sid = (yield entity_info.has_feature(account, account.bare_jid, Xep.UniqueStableStanzaIDs.NS_URI)) ||
|
||||||
(yield entity_info.has_feature(account, account.bare_jid, Xmpp.MessageArchiveManagement.NS_URI_2));
|
(yield entity_info.has_feature(account, account.bare_jid, Xmpp.MessageArchiveManagement.NS_URI));
|
||||||
if (server_supports_sid) {
|
if (server_supports_sid) {
|
||||||
new_message.server_id = Xep.UniqueStableStanzaIDs.get_stanza_id(message, account.bare_jid);
|
new_message.server_id = Xep.UniqueStableStanzaIDs.get_stanza_id(message, account.bare_jid);
|
||||||
}
|
}
|
||||||
|
@ -378,7 +380,7 @@ public class MessageProcessor : StreamInteractionModule, Object {
|
||||||
public override async bool run(Entities.Message message, Xmpp.MessageStanza stanza, Conversation conversation) {
|
public override async bool run(Entities.Message message, Xmpp.MessageStanza stanza, Conversation conversation) {
|
||||||
bool is_mam_message = Xmpp.MessageArchiveManagement.MessageFlag.get_flag(stanza) != null;
|
bool is_mam_message = Xmpp.MessageArchiveManagement.MessageFlag.get_flag(stanza) != null;
|
||||||
XmppStream? stream = stream_interactor.get_stream(conversation.account);
|
XmppStream? stream = stream_interactor.get_stream(conversation.account);
|
||||||
Xmpp.MessageArchiveManagement.Flag? mam_flag = stream != null ? stream.get_flag(Xmpp.MessageArchiveManagement.Flag.IDENTITY) : null;
|
Xmpp.MessageArchiveManagement.Flag mam_flag = Xmpp.MessageArchiveManagement.Flag.get_flag(stream);
|
||||||
if (is_mam_message || (mam_flag != null && mam_flag.cought_up == true)) {
|
if (is_mam_message || (mam_flag != null && mam_flag.cought_up == true)) {
|
||||||
conversation.account.mam_earliest_synced = message.local_time;
|
conversation.account.mam_earliest_synced = message.local_time;
|
||||||
}
|
}
|
||||||
|
@ -494,8 +496,7 @@ public class MessageProcessor : StreamInteractionModule, Object {
|
||||||
|
|
||||||
string fallback = FallbackBody.get_quoted_fallback_body(content_item);
|
string fallback = FallbackBody.get_quoted_fallback_body(content_item);
|
||||||
|
|
||||||
long fallback_length = fallback.length;
|
var fallback_location = new Xep.FallbackIndication.FallbackLocation(0, (int)fallback.char_count());
|
||||||
var fallback_location = new Xep.FallbackIndication.FallbackLocation(0, (int)fallback_length);
|
|
||||||
Xep.FallbackIndication.set_fallback(new_stanza, new Xep.FallbackIndication.Fallback(Xep.Replies.NS_URI, new Xep.FallbackIndication.FallbackLocation[] { fallback_location }));
|
Xep.FallbackIndication.set_fallback(new_stanza, new Xep.FallbackIndication.Fallback(Xep.Replies.NS_URI, new Xep.FallbackIndication.FallbackLocation[] { fallback_location }));
|
||||||
|
|
||||||
return fallback;
|
return fallback;
|
||||||
|
|
|
@ -72,7 +72,7 @@ public class MucManager : StreamInteractionModule, Object {
|
||||||
|
|
||||||
bool receive_history = true;
|
bool receive_history = true;
|
||||||
EntityInfo entity_info = stream_interactor.get_module(EntityInfo.IDENTITY);
|
EntityInfo entity_info = stream_interactor.get_module(EntityInfo.IDENTITY);
|
||||||
bool can_do_mam = yield entity_info.has_feature(account, jid, Xmpp.MessageArchiveManagement.NS_URI_2);
|
bool can_do_mam = yield entity_info.has_feature(account, jid, Xmpp.MessageArchiveManagement.NS_URI);
|
||||||
if (can_do_mam) {
|
if (can_do_mam) {
|
||||||
receive_history = false;
|
receive_history = false;
|
||||||
history_since = null;
|
history_since = null;
|
||||||
|
|
|
@ -64,7 +64,7 @@ public class Dino.Reactions : StreamInteractionModule, Object {
|
||||||
// The MUC server needs to 1) support stable stanza ids 2) either support occupant ids or be a private room (where we know real jids)
|
// The MUC server needs to 1) support stable stanza ids 2) either support occupant ids or be a private room (where we know real jids)
|
||||||
var entity_info = stream_interactor.get_module(EntityInfo.IDENTITY);
|
var entity_info = stream_interactor.get_module(EntityInfo.IDENTITY);
|
||||||
bool server_supports_sid = (entity_info.has_feature_cached(conversation.account, conversation.counterpart.bare_jid, Xep.UniqueStableStanzaIDs.NS_URI)) ||
|
bool server_supports_sid = (entity_info.has_feature_cached(conversation.account, conversation.counterpart.bare_jid, Xep.UniqueStableStanzaIDs.NS_URI)) ||
|
||||||
(entity_info.has_feature_cached(conversation.account, conversation.counterpart.bare_jid, Xmpp.MessageArchiveManagement.NS_URI_2));
|
(entity_info.has_feature_cached(conversation.account, conversation.counterpart.bare_jid, Xmpp.MessageArchiveManagement.NS_URI));
|
||||||
if (!server_supports_sid) return false;
|
if (!server_supports_sid) return false;
|
||||||
|
|
||||||
bool? supports_occupant_ids = entity_info.has_feature_cached(conversation.account, conversation.counterpart, Xep.OccupantIds.NS_URI);
|
bool? supports_occupant_ids = entity_info.has_feature_cached(conversation.account, conversation.counterpart, Xep.OccupantIds.NS_URI);
|
||||||
|
|
|
@ -105,7 +105,8 @@ namespace Dino {
|
||||||
string body = message.body;
|
string body = message.body;
|
||||||
foreach (var fallback in message.get_fallbacks()) {
|
foreach (var fallback in message.get_fallbacks()) {
|
||||||
if (fallback.ns_uri == Xep.Replies.NS_URI && message.quoted_item_id > 0) {
|
if (fallback.ns_uri == Xep.Replies.NS_URI && message.quoted_item_id > 0) {
|
||||||
body = body[0:fallback.locations[0].from_char] + body[fallback.locations[0].to_char:body.length];
|
body = body[0:body.index_of_nth_char(fallback.locations[0].from_char)] +
|
||||||
|
body[body.index_of_nth_char(fallback.locations[0].to_char):body.length];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return body;
|
return body;
|
||||||
|
|
|
@ -34,6 +34,7 @@ namespace Dino {
|
||||||
if (self_word != null && (account.alias == null || account.alias.length == 0)) {
|
if (self_word != null && (account.alias == null || account.alias.length == 0)) {
|
||||||
return self_word;
|
return self_word;
|
||||||
}
|
}
|
||||||
|
if (account.alias != null && account.alias.length == 0) return null;
|
||||||
return account.alias;
|
return account.alias;
|
||||||
}
|
}
|
||||||
Roster.Item roster_item = stream_interactor.get_module(RosterManager.IDENTITY).get_roster_item(account, jid);
|
Roster.Item roster_item = stream_interactor.get_module(RosterManager.IDENTITY).get_roster_item(account, jid);
|
||||||
|
|
|
@ -85,6 +85,7 @@
|
||||||
<property name="margin_bottom">3</property>
|
<property name="margin_bottom">3</property>
|
||||||
<property name="margin_start">14</property>
|
<property name="margin_start">14</property>
|
||||||
<property name="margin_end">14</property>
|
<property name="margin_end">14</property>
|
||||||
|
<property name="wrap">True</property>
|
||||||
<attributes>
|
<attributes>
|
||||||
<attribute name="scale" value="0.8"></attribute>
|
<attribute name="scale" value="0.8"></attribute>
|
||||||
</attributes>
|
</attributes>
|
||||||
|
|
|
@ -5,9 +5,10 @@ GenericName=Jabber/XMPP Client
|
||||||
Keywords=chat;talk;im;message;xmpp;jabber;
|
Keywords=chat;talk;im;message;xmpp;jabber;
|
||||||
Exec=dino %U
|
Exec=dino %U
|
||||||
Icon=im.dino.Dino
|
Icon=im.dino.Dino
|
||||||
StartupNotify=false
|
StartupNotify=true
|
||||||
Terminal=false
|
Terminal=false
|
||||||
Type=Application
|
Type=Application
|
||||||
Categories=GTK;Network;Chat;InstantMessaging;
|
Categories=GTK;Network;Chat;InstantMessaging;
|
||||||
X-GNOME-UsesNotifications=true
|
X-GNOME-UsesNotifications=true
|
||||||
MimeType=x-scheme-handler/xmpp;
|
MimeType=x-scheme-handler/xmpp;
|
||||||
|
X-Purism-FormFactor=Workstation;Mobile;
|
||||||
|
|
|
@ -392,17 +392,13 @@ box.dino-input-error .chat-input-status.input-status-highlight-once {
|
||||||
text-shadow: 0 0 2px black;
|
text-shadow: 0 0 2px black;
|
||||||
}
|
}
|
||||||
|
|
||||||
.dino-call-window .call-header-background {
|
.dino-call-window .participant-header-bar {
|
||||||
background: linear-gradient(rgba(0,0,0,0.4), rgba(0,0,0,0));
|
background: linear-gradient(rgba(0,0,0,0.4), rgba(0,0,0,0));
|
||||||
border: 0;
|
border: 0;
|
||||||
border-radius: 0;
|
border-radius: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.dino-call-window .participant-header-bar button {
|
.dino-call-window .participant-header-bar button:hover:not(.close) {
|
||||||
background: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.dino-call-window .participant-header-bar button:hover {
|
|
||||||
background: rgba(255,255,255,0.15);
|
background: rgba(255,255,255,0.15);
|
||||||
border-color: rgba(255,255,255,0);
|
border-color: rgba(255,255,255,0);
|
||||||
box-shadow: none;
|
box-shadow: none;
|
||||||
|
|
|
@ -22,7 +22,12 @@ namespace Dino.Ui {
|
||||||
private HashMap<string, ParticipantWidget> participant_widgets = new HashMap<string, ParticipantWidget>();
|
private HashMap<string, ParticipantWidget> participant_widgets = new HashMap<string, ParticipantWidget>();
|
||||||
private ArrayList<string> participants = new ArrayList<string>();
|
private ArrayList<string> participants = new ArrayList<string>();
|
||||||
|
|
||||||
|
private EventControllerFocus this_focus_events = new EventControllerFocus();
|
||||||
|
private GestureClick this_gesture_events = new GestureClick() { touch_only=true, propagation_phase=Gtk.PropagationPhase.CAPTURE };
|
||||||
private EventControllerMotion this_motion_events = new EventControllerMotion();
|
private EventControllerMotion this_motion_events = new EventControllerMotion();
|
||||||
|
private double latest_motion_x = -1;
|
||||||
|
private double latest_motion_y = -1;
|
||||||
|
private const double MOTION_RELEVANCE_THRESHOLD = 2;
|
||||||
|
|
||||||
private int own_video_width = 150;
|
private int own_video_width = 150;
|
||||||
private int own_video_height = 100;
|
private int own_video_height = 100;
|
||||||
|
@ -54,9 +59,20 @@ namespace Dino.Ui {
|
||||||
this.bind_property("controls-active", bottom_bar_revealer, "reveal-child", BindingFlags.SYNC_CREATE);
|
this.bind_property("controls-active", bottom_bar_revealer, "reveal-child", BindingFlags.SYNC_CREATE);
|
||||||
|
|
||||||
((Widget) this).add_controller(this_motion_events);
|
((Widget) this).add_controller(this_motion_events);
|
||||||
this_motion_events.motion.connect(reveal_control_elements);
|
this_motion_events.motion.connect((x, y) => {
|
||||||
this_motion_events.enter.connect(reveal_control_elements);
|
if ((latest_motion_x - x).abs() <= MOTION_RELEVANCE_THRESHOLD && (latest_motion_y - y).abs() <= MOTION_RELEVANCE_THRESHOLD) return;
|
||||||
this_motion_events.leave.connect(reveal_control_elements);
|
|
||||||
|
latest_motion_x = x;
|
||||||
|
latest_motion_y = y;
|
||||||
|
reveal_control_elements();
|
||||||
|
});
|
||||||
|
|
||||||
|
((Widget) this).add_controller(this_focus_events);
|
||||||
|
this_focus_events.enter.connect(reveal_control_elements);
|
||||||
|
this_focus_events.leave.connect(reveal_control_elements);
|
||||||
|
|
||||||
|
((Widget) this).add_controller(this_gesture_events);
|
||||||
|
this_gesture_events.pressed.connect_after(reveal_control_elements);
|
||||||
|
|
||||||
this.notify["default-width"].connect(reveal_control_elements);
|
this.notify["default-width"].connect(reveal_control_elements);
|
||||||
this.notify["default-height"].connect(reveal_control_elements);
|
this.notify["default-height"].connect(reveal_control_elements);
|
||||||
|
|
|
@ -74,14 +74,11 @@ namespace Dino.Ui {
|
||||||
|
|
||||||
header_bar.show_title_buttons = is_highest_row;
|
header_bar.show_title_buttons = is_highest_row;
|
||||||
if (is_highest_row) {
|
if (is_highest_row) {
|
||||||
header_bar.add_css_class("call-header-background");
|
|
||||||
Gtk.Settings? gtk_settings = Gtk.Settings.get_default();
|
Gtk.Settings? gtk_settings = Gtk.Settings.get_default();
|
||||||
if (gtk_settings != null) {
|
if (gtk_settings != null) {
|
||||||
string[] buttons = gtk_settings.gtk_decoration_layout.split(":");
|
string[] buttons = gtk_settings.gtk_decoration_layout.split(":");
|
||||||
header_bar.decoration_layout = (is_start ? buttons[0] : "") + ":" + (is_end && buttons.length == 2 ? buttons[1] : "");
|
header_bar.decoration_layout = (is_start ? buttons[0] : "") + ":" + (is_end && buttons.length == 2 ? buttons[1] : "");
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
header_bar.remove_css_class("call-header-background");
|
|
||||||
}
|
}
|
||||||
reveal_or_hide_controls();
|
reveal_or_hide_controls();
|
||||||
}
|
}
|
||||||
|
|
|
@ -94,8 +94,13 @@ public class ChatTextView : Box {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool on_text_input_key_press(uint keyval, uint keycode, Gdk.ModifierType state) {
|
private bool on_text_input_key_press(EventControllerKey controller, uint keyval, uint keycode, Gdk.ModifierType state) {
|
||||||
if (keyval in new uint[]{ Key.Return, Key.KP_Enter }) {
|
if (keyval in new uint[]{ Key.Return, Key.KP_Enter }) {
|
||||||
|
// Allow the text view to process the event. Needed for IME.
|
||||||
|
if (text_view.im_context_filter_keypress(controller.get_current_event())) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
if ((state & ModifierType.SHIFT_MASK) > 0) {
|
if ((state & ModifierType.SHIFT_MASK) > 0) {
|
||||||
text_view.buffer.insert_at_cursor("\n", 1);
|
text_view.buffer.insert_at_cursor("\n", 1);
|
||||||
} else if (text_view.buffer.text.strip() != "") {
|
} else if (text_view.buffer.text.strip() != "") {
|
||||||
|
|
|
@ -39,6 +39,8 @@ public class View : Box {
|
||||||
chooser.emoji_picked.connect((emoji) => {
|
chooser.emoji_picked.connect((emoji) => {
|
||||||
chat_text_view.text_view.buffer.insert_at_cursor(emoji, emoji.data.length);
|
chat_text_view.text_view.buffer.insert_at_cursor(emoji, emoji.data.length);
|
||||||
});
|
});
|
||||||
|
chooser.closed.connect(do_focus);
|
||||||
|
|
||||||
emoji_button.set_popover(chooser);
|
emoji_button.set_popover(chooser);
|
||||||
|
|
||||||
file_button.tooltip_text = Util.string_if_tooltips_active(_("Send a file"));
|
file_button.tooltip_text = Util.string_if_tooltips_active(_("Send a file"));
|
||||||
|
|
|
@ -95,7 +95,6 @@ public class ConversationView : Widget, Plugins.ConversationItemCollection, Plug
|
||||||
EventControllerMotion main_wrap_motion_events = new EventControllerMotion();
|
EventControllerMotion main_wrap_motion_events = new EventControllerMotion();
|
||||||
main_wrap_box.add_controller(main_wrap_motion_events);
|
main_wrap_box.add_controller(main_wrap_motion_events);
|
||||||
main_wrap_motion_events.leave.connect(on_leave_notify_event);
|
main_wrap_motion_events.leave.connect(on_leave_notify_event);
|
||||||
main_wrap_motion_events.enter.connect(update_highlight);
|
|
||||||
// The buttons of the overlaying message_menu_box may partially overlap the adjacent
|
// The buttons of the overlaying message_menu_box may partially overlap the adjacent
|
||||||
// conversation items. We connect to the main_event_box directly to avoid emitting
|
// conversation items. We connect to the main_event_box directly to avoid emitting
|
||||||
// the pointer motion events as long as the pointer is above the message menu.
|
// the pointer motion events as long as the pointer is above the message menu.
|
||||||
|
|
|
@ -225,7 +225,21 @@ public class ConversationSelectorRow : ListBoxRow {
|
||||||
label.attributes = copy;
|
label.attributes = copy;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private bool update_read_pending = false;
|
||||||
|
private bool update_read_pending_force = false;
|
||||||
protected void update_read(bool force_update = false) {
|
protected void update_read(bool force_update = false) {
|
||||||
|
if (force_update) update_read_pending_force = true;
|
||||||
|
if (update_read_pending) return;
|
||||||
|
update_read_pending = true;
|
||||||
|
Idle.add(() => {
|
||||||
|
update_read_pending = false;
|
||||||
|
update_read_pending_force = false;
|
||||||
|
update_read_idle(update_read_pending_force);
|
||||||
|
return Source.REMOVE;
|
||||||
|
}, Priority.LOW);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void update_read_idle(bool force_update = false) {
|
||||||
int current_num_unread = stream_interactor.get_module(ChatInteraction.IDENTITY).get_num_unread(conversation);
|
int current_num_unread = stream_interactor.get_module(ChatInteraction.IDENTITY).get_num_unread(conversation);
|
||||||
if (num_unread == current_num_unread && !force_update) return;
|
if (num_unread == current_num_unread && !force_update) return;
|
||||||
num_unread = current_num_unread;
|
num_unread = current_num_unread;
|
||||||
|
|
|
@ -10,13 +10,16 @@ public class FileProvider : Dino.FileProvider, Object {
|
||||||
|
|
||||||
private StreamInteractor stream_interactor;
|
private StreamInteractor stream_interactor;
|
||||||
private Dino.Database dino_db;
|
private Dino.Database dino_db;
|
||||||
|
private Soup.Session session;
|
||||||
private static Regex http_url_regex = /^https?:\/\/([^\s#]*)$/; // Spaces are invalid in URLs and we can't use fragments for downloads
|
private static Regex http_url_regex = /^https?:\/\/([^\s#]*)$/; // Spaces are invalid in URLs and we can't use fragments for downloads
|
||||||
private static Regex omemo_url_regex = /^aesgcm:\/\/(.*)#(([A-Fa-f0-9]{2}){48}|([A-Fa-f0-9]{2}){44})$/;
|
private static Regex omemo_url_regex = /^aesgcm:\/\/(.*)#(([A-Fa-f0-9]{2}){48}|([A-Fa-f0-9]{2}){44})$/;
|
||||||
|
|
||||||
public FileProvider(StreamInteractor stream_interactor, Dino.Database dino_db) {
|
public FileProvider(StreamInteractor stream_interactor, Dino.Database dino_db) {
|
||||||
this.stream_interactor = stream_interactor;
|
this.stream_interactor = stream_interactor;
|
||||||
this.dino_db = dino_db;
|
this.dino_db = dino_db;
|
||||||
|
this.session = new Soup.Session();
|
||||||
|
|
||||||
|
session.user_agent = @"Dino/$(Dino.get_short_version()) ";
|
||||||
stream_interactor.get_module(MessageProcessor.IDENTITY).received_pipeline.connect(new ReceivedMessageListener(this));
|
stream_interactor.get_module(MessageProcessor.IDENTITY).received_pipeline.connect(new ReceivedMessageListener(this));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -114,8 +117,6 @@ public class FileProvider : Dino.FileProvider, Object {
|
||||||
HttpFileReceiveData? http_receive_data = receive_data as HttpFileReceiveData;
|
HttpFileReceiveData? http_receive_data = receive_data as HttpFileReceiveData;
|
||||||
if (http_receive_data == null) return file_meta;
|
if (http_receive_data == null) return file_meta;
|
||||||
|
|
||||||
var session = new Soup.Session();
|
|
||||||
session.user_agent = @"Dino/$(Dino.get_short_version()) ";
|
|
||||||
var head_message = new Soup.Message("HEAD", http_receive_data.url);
|
var head_message = new Soup.Message("HEAD", http_receive_data.url);
|
||||||
head_message.request_headers.append("Accept-Encoding", "identity");
|
head_message.request_headers.append("Accept-Encoding", "identity");
|
||||||
|
|
||||||
|
@ -150,8 +151,6 @@ public class FileProvider : Dino.FileProvider, Object {
|
||||||
HttpFileReceiveData? http_receive_data = receive_data as HttpFileReceiveData;
|
HttpFileReceiveData? http_receive_data = receive_data as HttpFileReceiveData;
|
||||||
if (http_receive_data == null) assert(false);
|
if (http_receive_data == null) assert(false);
|
||||||
|
|
||||||
var session = new Soup.Session();
|
|
||||||
session.user_agent = @"Dino/$(Dino.get_short_version()) ";
|
|
||||||
var get_message = new Soup.Message("GET", http_receive_data.url);
|
var get_message = new Soup.Message("GET", http_receive_data.url);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
|
|
@ -7,12 +7,15 @@ namespace Dino.Plugins.HttpFiles {
|
||||||
public class HttpFileSender : FileSender, Object {
|
public class HttpFileSender : FileSender, Object {
|
||||||
private StreamInteractor stream_interactor;
|
private StreamInteractor stream_interactor;
|
||||||
private Database db;
|
private Database db;
|
||||||
|
private Soup.Session session;
|
||||||
private HashMap<Account, long> max_file_sizes = new HashMap<Account, long>(Account.hash_func, Account.equals_func);
|
private HashMap<Account, long> max_file_sizes = new HashMap<Account, long>(Account.hash_func, Account.equals_func);
|
||||||
|
|
||||||
public HttpFileSender(StreamInteractor stream_interactor, Database db) {
|
public HttpFileSender(StreamInteractor stream_interactor, Database db) {
|
||||||
this.stream_interactor = stream_interactor;
|
this.stream_interactor = stream_interactor;
|
||||||
this.db = db;
|
this.db = db;
|
||||||
|
this.session = new Soup.Session();
|
||||||
|
|
||||||
|
session.user_agent = @"Dino/$(Dino.get_short_version()) ";
|
||||||
stream_interactor.stream_negotiated.connect(on_stream_negotiated);
|
stream_interactor.stream_negotiated.connect(on_stream_negotiated);
|
||||||
stream_interactor.get_module(MessageProcessor.IDENTITY).build_message_stanza.connect(check_add_oob);
|
stream_interactor.get_module(MessageProcessor.IDENTITY).build_message_stanza.connect(check_add_oob);
|
||||||
}
|
}
|
||||||
|
@ -90,8 +93,6 @@ public class HttpFileSender : FileSender, Object {
|
||||||
Xmpp.XmppStream? stream = stream_interactor.get_stream(file_transfer.account);
|
Xmpp.XmppStream? stream = stream_interactor.get_stream(file_transfer.account);
|
||||||
if (stream == null) return;
|
if (stream == null) return;
|
||||||
|
|
||||||
var session = new Soup.Session();
|
|
||||||
session.user_agent = @"Dino/$(Dino.get_short_version()) ";
|
|
||||||
var put_message = new Soup.Message("PUT", file_send_data.url_up);
|
var put_message = new Soup.Message("PUT", file_send_data.url_up);
|
||||||
#if SOUP_3_0
|
#if SOUP_3_0
|
||||||
put_message.set_request_body(file_meta.mime_type, file_transfer.input_stream, (ssize_t) file_meta.size);
|
put_message.set_request_body(file_meta.mime_type, file_transfer.input_stream, (ssize_t) file_meta.size);
|
||||||
|
|
|
@ -38,7 +38,11 @@ public class Handler {
|
||||||
}
|
}
|
||||||
|
|
||||||
public uint8[]? process_incoming_data(uint component_id, uint8[] data) throws Crypto.Error {
|
public uint8[]? process_incoming_data(uint component_id, uint8[] data) throws Crypto.Error {
|
||||||
if (srtp_session.has_decrypt) {
|
if (data[0] >= 128) {
|
||||||
|
if (!srtp_session.has_decrypt) {
|
||||||
|
debug("Received data before SRTP session is ready, dropping.");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
if (component_id == 1) {
|
if (component_id == 1) {
|
||||||
if (data.length >= 2 && data[1] >= 192 && data[1] < 224) {
|
if (data.length >= 2 && data[1] >= 192 && data[1] < 224) {
|
||||||
return srtp_session.decrypt_rtcp(data);
|
return srtp_session.decrypt_rtcp(data);
|
||||||
|
@ -46,9 +50,12 @@ public class Handler {
|
||||||
return srtp_session.decrypt_rtp(data);
|
return srtp_session.decrypt_rtp(data);
|
||||||
}
|
}
|
||||||
if (component_id == 2) return srtp_session.decrypt_rtcp(data);
|
if (component_id == 2) return srtp_session.decrypt_rtcp(data);
|
||||||
} else if (component_id == 1) {
|
|
||||||
on_data_rec(data);
|
|
||||||
}
|
}
|
||||||
|
if (component_id == 1 && data.length >= 1 && (data[0] >= 20 && data[0] < 64)) {
|
||||||
|
on_data_rec(data);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
debug("Dropping unknown data from component %u", component_id);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -79,7 +86,7 @@ public class Handler {
|
||||||
err = private_key.generate(PKAlgorithm.ECDSA, 256);
|
err = private_key.generate(PKAlgorithm.ECDSA, 256);
|
||||||
throw_if_error(err);
|
throw_if_error(err);
|
||||||
|
|
||||||
var start_time = new DateTime.now_local().add_days(1);
|
var start_time = new DateTime.now_local().add_days(-1);
|
||||||
var end_time = start_time.add_days(2);
|
var end_time = start_time.add_days(2);
|
||||||
|
|
||||||
X509.Certificate cert = X509.Certificate.create();
|
X509.Certificate cert = X509.Certificate.create();
|
||||||
|
|
|
@ -215,7 +215,12 @@ public class Dino.Plugins.Rtp.Device : MediaDevice, Object {
|
||||||
}
|
}
|
||||||
if (new_width == active_caps_width) return;
|
if (new_width == active_caps_width) return;
|
||||||
int new_height = device_caps_height * new_width / device_caps_width;
|
int new_height = device_caps_height * new_width / device_caps_width;
|
||||||
Gst.Caps new_caps = new Gst.Caps.simple("video/x-raw", "width", typeof(int), new_width, "height", typeof(int), new_height, "framerate", typeof(Gst.Fraction), device_caps_framerate_num, device_caps_framerate_den, null);
|
Gst.Caps new_caps;
|
||||||
|
if (device_caps_framerate_den != 0) {
|
||||||
|
new_caps = new Gst.Caps.simple("video/x-raw", "width", typeof(int), new_width, "height", typeof(int), new_height, "framerate", typeof(Gst.Fraction), device_caps_framerate_num, device_caps_framerate_den, null);
|
||||||
|
} else {
|
||||||
|
new_caps = new Gst.Caps.simple("video/x-raw", "width", typeof(int), new_width, "height", typeof(int), new_height, null);
|
||||||
|
}
|
||||||
double required_bitrate = get_target_bitrate(new_caps);
|
double required_bitrate = get_target_bitrate(new_caps);
|
||||||
debug("Changing resolution width from %d to %d (requires bitrate %f, current target is %u)", active_caps_width, new_width, required_bitrate, bitrate);
|
debug("Changing resolution width from %d to %d (requires bitrate %f, current target is %u)", active_caps_width, new_width, required_bitrate, bitrate);
|
||||||
if (bitrate < required_bitrate && new_width > active_caps_width) return;
|
if (bitrate < required_bitrate && new_width > active_caps_width) return;
|
||||||
|
@ -347,7 +352,7 @@ public class Dino.Plugins.Rtp.Device : MediaDevice, Object {
|
||||||
if (media == "audio") {
|
if (media == "audio") {
|
||||||
return Gst.Caps.from_string("audio/x-raw,rate=48000,channels=1");
|
return Gst.Caps.from_string("audio/x-raw,rate=48000,channels=1");
|
||||||
} else if (media == "video" && device.caps.get_size() > 0) {
|
} else if (media == "video" && device.caps.get_size() > 0) {
|
||||||
int best_index = 0;
|
int best_index = -1;
|
||||||
Value? best_fraction = null;
|
Value? best_fraction = null;
|
||||||
int best_fps = 0;
|
int best_fps = 0;
|
||||||
int best_width = 0;
|
int best_width = 0;
|
||||||
|
@ -390,10 +395,25 @@ public class Dino.Plugins.Rtp.Device : MediaDevice, Object {
|
||||||
best_fraction = best_fraction_now;
|
best_fraction = best_fraction_now;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (best_index == -1) {
|
||||||
|
// No caps in first round, try without framerate
|
||||||
|
for (int i = 0; i < device.caps.get_size(); i++) {
|
||||||
|
unowned Gst.Structure? that = device.caps.get_structure(i);
|
||||||
|
if (!that.has_name("video/x-raw")) continue;
|
||||||
|
int width = 0, height = 0;
|
||||||
|
if (!that.has_field("width") || !that.get_int("width", out width)) continue;
|
||||||
|
if (!that.has_field("height") || !that.get_int("height", out height)) continue;
|
||||||
|
if (best_width < width || best_width == width && best_height < height) {
|
||||||
|
best_width = width;
|
||||||
|
best_height = height;
|
||||||
|
best_index = i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Gst.Caps res = caps_copy_nth(device.caps, best_index);
|
Gst.Caps res = caps_copy_nth(device.caps, best_index);
|
||||||
unowned Gst.Structure? that = res.get_structure(0);
|
unowned Gst.Structure? that = res.get_structure(0);
|
||||||
Value framerate = that.get_value("framerate");
|
Value? framerate = that.get_value("framerate");
|
||||||
if (framerate.type() == typeof(Gst.ValueList)) {
|
if (framerate != null && framerate.type() == typeof(Gst.ValueList) && best_fraction != null) {
|
||||||
that.set_value("framerate", best_fraction);
|
that.set_value("framerate", best_fraction);
|
||||||
}
|
}
|
||||||
debug("Selected caps %s", res.to_string());
|
debug("Selected caps %s", res.to_string());
|
||||||
|
|
|
@ -426,11 +426,11 @@ public class Dino.Plugins.Rtp.Plugin : RootInterface, VideoCallPlugin, Object {
|
||||||
|
|
||||||
if (media == "video") {
|
if (media == "video") {
|
||||||
// Pick best FPS
|
// Pick best FPS
|
||||||
int max_fps = 0;
|
int max_fps = -1;
|
||||||
Device? max_fps_device = null;
|
Device? max_fps_device = null;
|
||||||
foreach (Device device in devices) {
|
foreach (Device device in devices) {
|
||||||
int fps = get_max_fps(device);
|
int fps = get_max_fps(device);
|
||||||
if (fps > max_fps) {
|
if (fps > max_fps || max_fps_device == null) {
|
||||||
max_fps = fps;
|
max_fps = fps;
|
||||||
max_fps_device = device;
|
max_fps_device = device;
|
||||||
}
|
}
|
||||||
|
|
|
@ -102,6 +102,9 @@ public class Dino.Plugins.Rtp.Stream : Xmpp.Xep.JingleRtp.Stream {
|
||||||
send_rtp.drop = true;
|
send_rtp.drop = true;
|
||||||
send_rtp.wait_on_eos = false;
|
send_rtp.wait_on_eos = false;
|
||||||
send_rtp.new_sample.connect(on_new_sample);
|
send_rtp.new_sample.connect(on_new_sample);
|
||||||
|
#if GST_1_20
|
||||||
|
send_rtp.new_serialized_event.connect(on_new_event);
|
||||||
|
#endif
|
||||||
send_rtp.connect("signal::eos", on_eos_static, this);
|
send_rtp.connect("signal::eos", on_eos_static, this);
|
||||||
pipe.add(send_rtp);
|
pipe.add(send_rtp);
|
||||||
|
|
||||||
|
@ -294,6 +297,60 @@ public class Dino.Plugins.Rtp.Stream : Xmpp.Xep.JingleRtp.Stream {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool flip = false;
|
||||||
|
uint8 rotation = 0;
|
||||||
|
#if GST_1_20
|
||||||
|
private bool on_new_event(Gst.App.Sink sink) {
|
||||||
|
if (sink == null || sink != send_rtp) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
Gst.MiniObject obj = sink.try_pull_object(0);
|
||||||
|
if (obj.type == typeof(Gst.Event)) {
|
||||||
|
unowned Gst.TagList tags;
|
||||||
|
if (((Gst.Event)obj).type == Gst.EventType.TAG) {
|
||||||
|
((Gst.Event)obj).parse_tag(out tags);
|
||||||
|
Gst.Video.OrientationMethod orientation_method;
|
||||||
|
Gst.Video.Orientation.from_tag(tags, out orientation_method);
|
||||||
|
switch (orientation_method) {
|
||||||
|
case Gst.Video.OrientationMethod.IDENTITY:
|
||||||
|
case Gst.Video.OrientationMethod.VERT:
|
||||||
|
default:
|
||||||
|
rotation = 0;
|
||||||
|
break;
|
||||||
|
case Gst.Video.OrientationMethod.@90R:
|
||||||
|
case Gst.Video.OrientationMethod.UL_LR:
|
||||||
|
rotation = 1;
|
||||||
|
break;
|
||||||
|
case Gst.Video.OrientationMethod.@180:
|
||||||
|
case Gst.Video.OrientationMethod.HORIZ:
|
||||||
|
rotation = 2;
|
||||||
|
break;
|
||||||
|
case Gst.Video.OrientationMethod.@90L:
|
||||||
|
case Gst.Video.OrientationMethod.UR_LL:
|
||||||
|
rotation = 3;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
switch (orientation_method) {
|
||||||
|
case Gst.Video.OrientationMethod.IDENTITY:
|
||||||
|
case Gst.Video.OrientationMethod.@90R:
|
||||||
|
case Gst.Video.OrientationMethod.@180:
|
||||||
|
case Gst.Video.OrientationMethod.@90L:
|
||||||
|
default:
|
||||||
|
flip = false;
|
||||||
|
break;
|
||||||
|
case Gst.Video.OrientationMethod.VERT:
|
||||||
|
case Gst.Video.OrientationMethod.UL_LR:
|
||||||
|
case Gst.Video.OrientationMethod.HORIZ:
|
||||||
|
case Gst.Video.OrientationMethod.UR_LL:
|
||||||
|
flip = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
private Gst.FlowReturn on_new_sample(Gst.App.Sink sink) {
|
private Gst.FlowReturn on_new_sample(Gst.App.Sink sink) {
|
||||||
if (sink == null) {
|
if (sink == null) {
|
||||||
debug("Sink is null");
|
debug("Sink is null");
|
||||||
|
@ -323,6 +380,24 @@ public class Dino.Plugins.Rtp.Stream : Xmpp.Xep.JingleRtp.Stream {
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#if GST_1_20
|
||||||
|
if (sink == send_rtp) {
|
||||||
|
Xmpp.Xep.JingleRtp.HeaderExtension? ext = header_extensions.first_match((it) => it.uri == "urn:3gpp:video-orientation");
|
||||||
|
if (ext != null) {
|
||||||
|
buffer = (Gst.Buffer) buffer.make_writable();
|
||||||
|
Gst.RTP.Buffer rtp_buffer;
|
||||||
|
if (Gst.RTP.Buffer.map(buffer, Gst.MapFlags.WRITE, out rtp_buffer)) {
|
||||||
|
uint8[] extension_data = new uint8[1];
|
||||||
|
bool camera = false;
|
||||||
|
extension_data[0] = extension_data[0] | (rotation & 0x3);
|
||||||
|
if (flip) extension_data[0] = extension_data[0] | 0x4;
|
||||||
|
if (camera) extension_data[0] = extension_data[0] | 0x8;
|
||||||
|
rtp_buffer.add_extension_onebyte_header(ext.id, extension_data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
prepare_local_crypto();
|
prepare_local_crypto();
|
||||||
|
|
||||||
uint8[] data;
|
uint8[] data;
|
||||||
|
@ -489,8 +564,8 @@ public class Dino.Plugins.Rtp.Stream : Xmpp.Xep.JingleRtp.Stream {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private uint16 previous_video_orientation_degree = uint16.MAX;
|
private uint16 previous_incoming_video_orientation_degree = uint16.MAX;
|
||||||
public signal void video_orientation_changed(uint16 degree);
|
public signal void incoming_video_orientation_changed(uint16 degree);
|
||||||
|
|
||||||
public override void on_recv_rtp_data(Bytes bytes) {
|
public override void on_recv_rtp_data(Bytes bytes) {
|
||||||
if (rtcp_mux && bytes.length >= 2 && bytes.get(1) >= 192 && bytes.get(1) < 224) {
|
if (rtcp_mux && bytes.length >= 2 && bytes.get(1) >= 192 && bytes.get(1) < 224) {
|
||||||
|
@ -545,9 +620,9 @@ public class Dino.Plugins.Rtp.Stream : Xmpp.Xep.JingleRtp.Stream {
|
||||||
case 2: rotation_degree = 180; break;
|
case 2: rotation_degree = 180; break;
|
||||||
case 3: rotation_degree = 270; break;
|
case 3: rotation_degree = 270; break;
|
||||||
}
|
}
|
||||||
if (rotation_degree != previous_video_orientation_degree) {
|
if (rotation_degree != previous_incoming_video_orientation_degree) {
|
||||||
video_orientation_changed(rotation_degree);
|
incoming_video_orientation_changed(rotation_degree);
|
||||||
previous_video_orientation_degree = rotation_degree;
|
previous_incoming_video_orientation_degree = rotation_degree;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -723,7 +798,7 @@ public class Dino.Plugins.Rtp.VideoStream : Stream {
|
||||||
private Gee.List<Gst.Element> outputs = new ArrayList<Gst.Element>();
|
private Gee.List<Gst.Element> outputs = new ArrayList<Gst.Element>();
|
||||||
private Gst.Element output_tee;
|
private Gst.Element output_tee;
|
||||||
private Gst.Element rotate;
|
private Gst.Element rotate;
|
||||||
private ulong video_orientation_changed_handler;
|
private ulong incoming_video_orientation_changed_handler;
|
||||||
|
|
||||||
public VideoStream(Plugin plugin, Xmpp.Xep.Jingle.Content content) {
|
public VideoStream(Plugin plugin, Xmpp.Xep.Jingle.Content content) {
|
||||||
base(plugin, content);
|
base(plugin, content);
|
||||||
|
@ -731,7 +806,7 @@ public class Dino.Plugins.Rtp.VideoStream : Stream {
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void create() {
|
public override void create() {
|
||||||
video_orientation_changed_handler = video_orientation_changed.connect(on_video_orientation_changed);
|
incoming_video_orientation_changed_handler = incoming_video_orientation_changed.connect(on_video_orientation_changed);
|
||||||
plugin.pause();
|
plugin.pause();
|
||||||
rotate = Gst.ElementFactory.make("videoflip", @"video_rotate_$rtpid");
|
rotate = Gst.ElementFactory.make("videoflip", @"video_rotate_$rtpid");
|
||||||
pipe.add(rotate);
|
pipe.add(rotate);
|
||||||
|
@ -780,7 +855,7 @@ public class Dino.Plugins.Rtp.VideoStream : Stream {
|
||||||
output_tee.set_state(Gst.State.NULL);
|
output_tee.set_state(Gst.State.NULL);
|
||||||
pipe.remove(output_tee);
|
pipe.remove(output_tee);
|
||||||
output_tee = null;
|
output_tee = null;
|
||||||
disconnect(video_orientation_changed_handler);
|
disconnect(incoming_video_orientation_changed_handler);
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void add_output(Gst.Element element, Xmpp.Jid? participant) {
|
public override void add_output(Gst.Element element, Xmpp.Jid? participant) {
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
private static extern unowned Gst.Video.Info gst_video_frame_get_video_info(Gst.Video.Frame frame);
|
private static extern unowned Gst.Video.Info gst_video_frame_get_video_info(Gst.Video.Frame frame);
|
||||||
|
[CCode (array_length_type = "size_t", type = "void*")]
|
||||||
private static extern unowned uint8[] gst_video_frame_get_data(Gst.Video.Frame frame);
|
private static extern unowned uint8[] gst_video_frame_get_data(Gst.Video.Frame frame);
|
||||||
|
|
||||||
public class Dino.Plugins.Rtp.Paintable : Gdk.Paintable, Object {
|
public class Dino.Plugins.Rtp.Paintable : Gdk.Paintable, Object {
|
||||||
|
@ -196,7 +197,11 @@ public class Dino.Plugins.Rtp.VideoWidget : Gtk.Widget, Dino.Plugins.VideoCallWi
|
||||||
caps.get_structure(0).get_int("width", out width);
|
caps.get_structure(0).get_int("width", out width);
|
||||||
caps.get_structure(0).get_int("height", out height);
|
caps.get_structure(0).get_int("height", out height);
|
||||||
debug("Input resolution changed: %ix%i", width, height);
|
debug("Input resolution changed: %ix%i", width, height);
|
||||||
|
// Invoke signal on GTK main loop as recipients are likely to use it for doing GTK operations
|
||||||
|
Idle.add(() => {
|
||||||
resolution_changed(width, height);
|
resolution_changed(width, height);
|
||||||
|
return Source.REMOVE;
|
||||||
|
});
|
||||||
last_input_caps = caps;
|
last_input_caps = caps;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -226,9 +231,21 @@ public class Dino.Plugins.Rtp.VideoWidget : Gtk.Widget, Dino.Plugins.VideoCallWi
|
||||||
if (connected_device == null) return;
|
if (connected_device == null) return;
|
||||||
plugin.pause();
|
plugin.pause();
|
||||||
pipe.add(sink);
|
pipe.add(sink);
|
||||||
|
#if GST_1_20
|
||||||
|
prepare = Gst.parse_bin_from_description(@"videoflip video-direction=auto name=video_widget_$(id)_orientation ! videoflip method=horizontal-flip name=video_widget_$(id)_flip ! videoconvert name=video_widget_$(id)_convert", true);
|
||||||
|
#else
|
||||||
prepare = Gst.parse_bin_from_description(@"videoflip method=horizontal-flip name=video_widget_$(id)_flip ! videoconvert name=video_widget_$(id)_convert", true);
|
prepare = Gst.parse_bin_from_description(@"videoflip method=horizontal-flip name=video_widget_$(id)_flip ! videoconvert name=video_widget_$(id)_convert", true);
|
||||||
|
#endif
|
||||||
prepare.name = @"video_widget_$(id)_prepare";
|
prepare.name = @"video_widget_$(id)_prepare";
|
||||||
|
#if GST_1_20
|
||||||
|
if (prepare is Gst.Bin) {
|
||||||
|
((Gst.Bin) prepare).get_by_name(@"video_widget_$(id)_flip").get_static_pad("sink").notify["caps"].connect(input_caps_changed);
|
||||||
|
} else {
|
||||||
|
#endif
|
||||||
prepare.get_static_pad("sink").notify["caps"].connect(input_caps_changed);
|
prepare.get_static_pad("sink").notify["caps"].connect(input_caps_changed);
|
||||||
|
#if GST_1_20
|
||||||
|
}
|
||||||
|
#endif
|
||||||
pipe.add(prepare);
|
pipe.add(prepare);
|
||||||
connected_device_element = connected_device.link_source();
|
connected_device_element = connected_device.link_source();
|
||||||
connected_device_element.link(prepare);
|
connected_device_element.link(prepare);
|
||||||
|
|
|
@ -58,6 +58,10 @@ public class ReceivedPipelineListener : StanzaListener<MessageStanza> {
|
||||||
warning("Received alleged carbon message from %s, ignoring", message.from.to_string());
|
warning("Received alleged carbon message from %s, ignoring", message.from.to_string());
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
if (message_node == null) {
|
||||||
|
warning("Received a carbon message with no message subnode in jabber:client namespace from %s, ignoring", message.from.to_string());
|
||||||
|
return true;
|
||||||
|
}
|
||||||
if (received_node != null) {
|
if (received_node != null) {
|
||||||
message.add_flag(new MessageFlag(MessageFlag.TYPE_RECEIVED));
|
message.add_flag(new MessageFlag(MessageFlag.TYPE_RECEIVED));
|
||||||
} else if (sent_node != null) {
|
} else if (sent_node != null) {
|
||||||
|
|
|
@ -58,7 +58,7 @@ namespace Xmpp.MessageArchiveManagement.V2 {
|
||||||
fields.add(field);
|
fields.add(field);
|
||||||
}
|
}
|
||||||
|
|
||||||
return MessageArchiveManagement.create_base_query(stream, MessageArchiveManagement.NS_URI_2, mam_params.query_id, fields);
|
return MessageArchiveManagement.create_base_query(stream, mam_params.query_id, fields);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async QueryResult query_archive(XmppStream stream, MamQueryParams mam_params, Cancellable? cancellable = null) {
|
public async QueryResult query_archive(XmppStream stream, MamQueryParams mam_params, Cancellable? cancellable = null) {
|
||||||
|
@ -67,14 +67,14 @@ namespace Xmpp.MessageArchiveManagement.V2 {
|
||||||
query_node.put_node(ResultSetManagement.create_set_rsm_node_before(mam_params.end_id));
|
query_node.put_node(ResultSetManagement.create_set_rsm_node_before(mam_params.end_id));
|
||||||
}
|
}
|
||||||
|
|
||||||
return yield MessageArchiveManagement.query_archive(stream, MessageArchiveManagement.NS_URI_2, mam_params.mam_server, query_node, cancellable);
|
return yield MessageArchiveManagement.query_archive(stream, mam_params.mam_server, query_node, cancellable);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async QueryResult page_through_results(XmppStream stream, MamQueryParams mam_params, QueryResult prev_result, Cancellable? cancellable = null) {
|
public async QueryResult page_through_results(XmppStream stream, MamQueryParams mam_params, QueryResult prev_result, Cancellable? cancellable = null) {
|
||||||
var query_node = create_base_query(stream, mam_params);
|
var query_node = create_base_query(stream, mam_params);
|
||||||
query_node.put_node(ResultSetManagement.create_set_rsm_node_before(prev_result.first));
|
query_node.put_node(ResultSetManagement.create_set_rsm_node_before(prev_result.first));
|
||||||
|
|
||||||
return yield MessageArchiveManagement.query_archive(stream, MessageArchiveManagement.NS_URI_2, mam_params.mam_server, query_node, cancellable);
|
return yield MessageArchiveManagement.query_archive(stream, mam_params.mam_server, query_node, cancellable);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -4,15 +4,13 @@ using Xmpp.Xep;
|
||||||
namespace Xmpp.MessageArchiveManagement {
|
namespace Xmpp.MessageArchiveManagement {
|
||||||
|
|
||||||
public const string NS_URI = "urn:xmpp:mam:2";
|
public const string NS_URI = "urn:xmpp:mam:2";
|
||||||
public const string NS_URI_2 = "urn:xmpp:mam:2";
|
|
||||||
public const string NS_URI_1 = "urn:xmpp:mam:1";
|
|
||||||
|
|
||||||
public class QueryResult {
|
public class QueryResult {
|
||||||
public bool error { get; set; default=false; }
|
public bool error { get; set; default=false; }
|
||||||
public bool malformed { get; set; default=false; }
|
public bool malformed { get; set; default=false; }
|
||||||
public bool complete { get; set; default=false; }
|
public bool complete { get; set; default=false; }
|
||||||
public string first { get; set; }
|
public string? first { get; set; }
|
||||||
public string last { get; set; }
|
public string? last { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
public class Module : XmppStreamModule {
|
public class Module : XmppStreamModule {
|
||||||
|
@ -36,45 +34,36 @@ public class Module : XmppStreamModule {
|
||||||
|
|
||||||
private async void query_availability(XmppStream stream) {
|
private async void query_availability(XmppStream stream) {
|
||||||
Jid own_jid = stream.get_flag(Bind.Flag.IDENTITY).my_jid.bare_jid;
|
Jid own_jid = stream.get_flag(Bind.Flag.IDENTITY).my_jid.bare_jid;
|
||||||
|
bool mam_available = yield stream.get_module(ServiceDiscovery.Module.IDENTITY).has_entity_feature(stream, own_jid, NS_URI);
|
||||||
bool ver_2_available = yield stream.get_module(ServiceDiscovery.Module.IDENTITY).has_entity_feature(stream, own_jid, NS_URI);
|
if (mam_available) {
|
||||||
if (ver_2_available) {
|
|
||||||
stream.add_flag(new Flag(NS_URI));
|
|
||||||
feature_available(stream);
|
feature_available(stream);
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool ver_1_available = yield stream.get_module(ServiceDiscovery.Module.IDENTITY).has_entity_feature(stream, own_jid, NS_URI_1);
|
|
||||||
if (ver_1_available) {
|
|
||||||
stream.add_flag(new Flag(NS_URI_1));
|
|
||||||
feature_available(stream);
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
internal StanzaNode create_base_query(XmppStream stream, string ns, string? queryid, Gee.List<DataForms.DataForm.Field> fields) {
|
internal StanzaNode create_base_query(XmppStream stream, string? queryid, Gee.List<DataForms.DataForm.Field> fields) {
|
||||||
DataForms.DataForm data_form = new DataForms.DataForm();
|
DataForms.DataForm data_form = new DataForms.DataForm();
|
||||||
|
|
||||||
DataForms.DataForm.HiddenField form_type_field = new DataForms.DataForm.HiddenField() { var="FORM_TYPE" };
|
DataForms.DataForm.HiddenField form_type_field = new DataForms.DataForm.HiddenField() { var="FORM_TYPE" };
|
||||||
form_type_field.set_value_string(NS_VER(stream));
|
form_type_field.set_value_string(NS_URI);
|
||||||
data_form.add_field(form_type_field);
|
data_form.add_field(form_type_field);
|
||||||
|
|
||||||
foreach (var field in fields) {
|
foreach (var field in fields) {
|
||||||
data_form.add_field(field);
|
data_form.add_field(field);
|
||||||
}
|
}
|
||||||
|
|
||||||
StanzaNode query_node = new StanzaNode.build("query", NS_VER(stream)).add_self_xmlns().put_node(data_form.get_submit_node());
|
StanzaNode query_node = new StanzaNode.build("query", NS_URI).add_self_xmlns().put_node(data_form.get_submit_node());
|
||||||
if (queryid != null) {
|
|
||||||
query_node.put_attribute("queryid", queryid);
|
query_node.put_attribute("queryid", queryid);
|
||||||
}
|
|
||||||
return query_node;
|
return query_node;
|
||||||
}
|
}
|
||||||
|
|
||||||
internal async QueryResult query_archive(XmppStream stream, string ns, Jid? mam_server, StanzaNode query_node, Cancellable? cancellable = null) {
|
internal async QueryResult query_archive(XmppStream stream, Jid? mam_server, StanzaNode query_node, Cancellable? cancellable = null) {
|
||||||
var res = new QueryResult();
|
|
||||||
|
|
||||||
if (stream.get_flag(Flag.IDENTITY) == null) { res.error = true; return res; }
|
var res = new QueryResult();
|
||||||
|
Flag flag = Flag.get_flag(stream);
|
||||||
|
string? query_id = query_node.get_attribute("queryid");
|
||||||
|
if (flag == null || query_id == null) { res.error = true; return res; }
|
||||||
|
flag.active_query_ids.add(query_id);
|
||||||
|
|
||||||
// Build and send query
|
// Build and send query
|
||||||
Iq.Stanza iq = new Iq.Stanza.set(query_node) { to=mam_server };
|
Iq.Stanza iq = new Iq.Stanza.set(query_node) { to=mam_server };
|
||||||
|
@ -82,7 +71,7 @@ public class Module : XmppStreamModule {
|
||||||
Iq.Stanza result_iq = yield stream.get_module(Iq.Module.IDENTITY).send_iq_async(stream, iq, Priority.LOW, cancellable);
|
Iq.Stanza result_iq = yield stream.get_module(Iq.Module.IDENTITY).send_iq_async(stream, iq, Priority.LOW, cancellable);
|
||||||
|
|
||||||
// Parse the response IQ into a QueryResult.
|
// Parse the response IQ into a QueryResult.
|
||||||
StanzaNode? fin_node = result_iq.stanza.get_subnode("fin", ns);
|
StanzaNode? fin_node = result_iq.stanza.get_subnode("fin", NS_URI);
|
||||||
if (fin_node == null) { res.malformed = true; return res; }
|
if (fin_node == null) { res.malformed = true; return res; }
|
||||||
|
|
||||||
StanzaNode? rsm_node = fin_node.get_subnode("set", Xmpp.ResultSetManagement.NS_URI);
|
StanzaNode? rsm_node = fin_node.get_subnode("set", Xmpp.ResultSetManagement.NS_URI);
|
||||||
|
@ -91,7 +80,12 @@ public class Module : XmppStreamModule {
|
||||||
res.first = rsm_node.get_deep_string_content("first");
|
res.first = rsm_node.get_deep_string_content("first");
|
||||||
res.last = rsm_node.get_deep_string_content("last");
|
res.last = rsm_node.get_deep_string_content("last");
|
||||||
if ((res.first == null) != (res.last == null)) { res.malformed = true; return res; }
|
if ((res.first == null) != (res.last == null)) { res.malformed = true; return res; }
|
||||||
res.complete = fin_node.get_attribute_bool("complete", false, ns);
|
res.complete = fin_node.get_attribute_bool("complete", false, NS_URI);
|
||||||
|
|
||||||
|
Idle.add(() => {
|
||||||
|
flag.active_query_ids.remove(query_id);
|
||||||
|
return Source.REMOVE;
|
||||||
|
}, Priority.LOW);
|
||||||
|
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
@ -104,14 +98,36 @@ public class ReceivedPipelineListener : StanzaListener<MessageStanza> {
|
||||||
public override string[] after_actions { get { return after_actions_const; } }
|
public override string[] after_actions { get { return after_actions_const; } }
|
||||||
|
|
||||||
public override async bool run(XmppStream stream, MessageStanza message) {
|
public override async bool run(XmppStream stream, MessageStanza message) {
|
||||||
if (stream.get_flag(Flag.IDENTITY) == null) return false;
|
Flag flag = Flag.get_flag(stream);
|
||||||
|
|
||||||
StanzaNode? message_node = message.stanza.get_deep_subnode(NS_VER(stream) + ":result", StanzaForwarding.NS_URI + ":forwarded", Xmpp.NS_URI + ":message");
|
StanzaNode? message_node = message.stanza.get_deep_subnode(NS_URI + ":result", StanzaForwarding.NS_URI + ":forwarded", Xmpp.NS_URI + ":message");
|
||||||
if (message_node != null) {
|
if (message_node != null) {
|
||||||
StanzaNode? forward_node = message.stanza.get_deep_subnode(NS_VER(stream) + ":result", StanzaForwarding.NS_URI + ":forwarded", DelayedDelivery.NS_URI + ":delay");
|
StanzaNode? forward_node = message.stanza.get_deep_subnode(NS_URI + ":result", StanzaForwarding.NS_URI + ":forwarded", DelayedDelivery.NS_URI + ":delay");
|
||||||
DateTime? datetime = DelayedDelivery.get_time_for_node(forward_node);
|
DateTime? datetime = DelayedDelivery.get_time_for_node(forward_node);
|
||||||
string? mam_id = message.stanza.get_deep_attribute(NS_VER(stream) + ":result", NS_VER(stream) + ":id");
|
string? mam_id = message.stanza.get_deep_attribute(NS_URI + ":result", NS_URI + ":id");
|
||||||
string? query_id = message.stanza.get_deep_attribute(NS_VER(stream) + ":result", NS_VER(stream) + ":queryid");
|
string? query_id = message.stanza.get_deep_attribute(NS_URI + ":result", NS_URI + ":queryid");
|
||||||
|
|
||||||
|
if (query_id == null) {
|
||||||
|
warning("Received MAM message without queryid from %s, ignoring", message.from.to_string());
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!flag.active_query_ids.contains(query_id)) {
|
||||||
|
warning("Received MAM message from %s with unknown query id %s, ignoring", message.from.to_string(), query_id ?? "<none>");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
Jid? inner_from = null;
|
||||||
|
try {
|
||||||
|
inner_from = new Jid(message_node.get_attribute("from"));
|
||||||
|
} catch (InvalidJidError e) {
|
||||||
|
warning("Received MAM message with invalid from attribute in forwarded message from %s, ignoring", message.from.to_string());
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (!message.from.equals(stream.get_flag(Bind.Flag.IDENTITY).my_jid.bare_jid) && !message.from.equals_bare(inner_from)) {
|
||||||
|
warning("Received MAM message from %s illegally impersonating %s, ignoring", message.from.to_string(), inner_from.to_string());
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
message.add_flag(new MessageFlag(message.from, datetime, mam_id, query_id));
|
message.add_flag(new MessageFlag(message.from, datetime, mam_id, query_id));
|
||||||
|
|
||||||
message.stanza = message_node;
|
message.stanza = message_node;
|
||||||
|
@ -124,10 +140,15 @@ public class ReceivedPipelineListener : StanzaListener<MessageStanza> {
|
||||||
public class Flag : XmppStreamFlag {
|
public class Flag : XmppStreamFlag {
|
||||||
public static FlagIdentity<Flag> IDENTITY = new FlagIdentity<Flag>(NS_URI, "message_archive_management");
|
public static FlagIdentity<Flag> IDENTITY = new FlagIdentity<Flag>(NS_URI, "message_archive_management");
|
||||||
public bool cought_up { get; set; default=false; }
|
public bool cought_up { get; set; default=false; }
|
||||||
public string ns_ver;
|
public Gee.Set<string> active_query_ids { get; set; default = new HashSet<string>(); }
|
||||||
|
|
||||||
public Flag(string ns_ver) {
|
public static Flag get_flag(XmppStream stream) {
|
||||||
this.ns_ver = ns_ver;
|
Flag? flag = stream.get_flag(Flag.IDENTITY);
|
||||||
|
if (flag == null) {
|
||||||
|
flag = new Flag();
|
||||||
|
stream.add_flag(flag);
|
||||||
|
}
|
||||||
|
return flag;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override string get_ns() { return NS_URI; }
|
public override string get_ns() { return NS_URI; }
|
||||||
|
@ -155,8 +176,4 @@ public class MessageFlag : Xmpp.MessageFlag {
|
||||||
public override string get_id() { return ID; }
|
public override string get_id() { return ID; }
|
||||||
}
|
}
|
||||||
|
|
||||||
private static string NS_VER(XmppStream stream) {
|
|
||||||
return stream.get_flag(Flag.IDENTITY).ns_ver;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
|
@ -68,6 +68,11 @@ public class Module : BookmarksProvider, XmppStreamModule {
|
||||||
}
|
}
|
||||||
|
|
||||||
private void on_pupsub_item(XmppStream stream, Jid jid, string id, StanzaNode? node) {
|
private void on_pupsub_item(XmppStream stream, Jid jid, string id, StanzaNode? node) {
|
||||||
|
if (!jid.equals(stream.get_flag(Bind.Flag.IDENTITY).my_jid.bare_jid)) {
|
||||||
|
warning("Received alleged bookmarks:1 item from %s, ignoring", jid.to_string());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
Conference conference = parse_item_node(node, id);
|
Conference conference = parse_item_node(node, id);
|
||||||
Flag? flag = stream.get_flag(Flag.IDENTITY);
|
Flag? flag = stream.get_flag(Flag.IDENTITY);
|
||||||
if (flag != null) {
|
if (flag != null) {
|
||||||
|
@ -77,6 +82,11 @@ public class Module : BookmarksProvider, XmppStreamModule {
|
||||||
}
|
}
|
||||||
|
|
||||||
private void on_pupsub_retract(XmppStream stream, Jid jid, string id) {
|
private void on_pupsub_retract(XmppStream stream, Jid jid, string id) {
|
||||||
|
if (!jid.equals(stream.get_flag(Bind.Flag.IDENTITY).my_jid.bare_jid)) {
|
||||||
|
warning("Received alleged bookmarks:1 retract from %s, ignoring", jid.to_string());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
Jid jid_parsed = new Jid(id);
|
Jid jid_parsed = new Jid(id);
|
||||||
Flag? flag = stream.get_flag(Flag.IDENTITY);
|
Flag? flag = stream.get_flag(Flag.IDENTITY);
|
||||||
|
|
Loading…
Reference in a new issue