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
|
||||
- run: sudo apt-get update
|
||||
- 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: make
|
||||
- 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 {
|
||||
|
||||
public class Database : Qlite.Database {
|
||||
private const int VERSION = 25;
|
||||
private const int VERSION = 26;
|
||||
|
||||
public class AccountTable : Table {
|
||||
public Column<int> id = new Column.Integer("id") { primary_key = true, auto_increment = true };
|
||||
|
@ -93,6 +93,11 @@ public class Database : Qlite.Database {
|
|||
|
||||
// deduplication
|
||||
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});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -90,11 +90,9 @@ public class Dino.HistorySync {
|
|||
if (!is_muc_mam && !from_our_server) return;
|
||||
|
||||
// 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;
|
||||
if (mam_flag == null) return;
|
||||
string? id = message.stanza.get_deep_attribute(mam_flag.ns_ver + ":result", "id");
|
||||
string? id = message.stanza.get_deep_attribute(Xmpp.MessageArchiveManagement.NS_URI + ":result", "id");
|
||||
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) {
|
||||
warning("MAM result did not contain delayed time %s", message.stanza.to_string());
|
||||
return;
|
||||
|
@ -104,7 +102,7 @@ public class Dino.HistorySync {
|
|||
mam_times[account][id] = time;
|
||||
|
||||
// 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]) {
|
||||
debug("[%s] Hitted range (id) %s", account.bare_jid.to_string(), id);
|
||||
hitted_range[query_id] = -2;
|
||||
|
@ -163,7 +161,7 @@ public class Dino.HistorySync {
|
|||
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());
|
||||
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;
|
||||
|
||||
RowOption previous_row_opt = db.mam_catchup.select()
|
||||
|
@ -214,13 +212,11 @@ public class Dino.HistorySync {
|
|||
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.
|
||||
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;
|
||||
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.
|
||||
** @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];
|
||||
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]);
|
||||
|
@ -282,9 +278,9 @@ public class Dino.HistorySync {
|
|||
earliest_time, earlier_range[db.mam_catchup.to_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);
|
||||
// Merge earlier range into later one.
|
||||
db.mam_catchup.update()
|
||||
|
@ -330,9 +326,9 @@ public class Dino.HistorySync {
|
|||
PageRequestResult? page_result = null;
|
||||
do {
|
||||
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;
|
||||
long earliest_mam_time = (long)mam_times[account][earliest_mam_id].to_unix();
|
||||
|
@ -357,7 +353,6 @@ public class Dino.HistorySync {
|
|||
MorePagesAvailable,
|
||||
TargetReached,
|
||||
NoMoreMessages,
|
||||
Duplicate,
|
||||
Error,
|
||||
Cancelled
|
||||
}
|
||||
|
@ -399,23 +394,25 @@ public class Dino.HistorySync {
|
|||
string query_id = query_params.query_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()) {
|
||||
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)
|
||||
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);
|
||||
if (mam_message_flag != null && mam_message_flag.mam_id != null) {
|
||||
if (after_id != null && mam_message_flag.mam_id == after_id) {
|
||||
// Successfully fetched the whole range
|
||||
yield send_messages_back_into_pipeline(account, query_id, cancellable);
|
||||
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
|
||||
yield send_messages_back_into_pipeline(account, query_id);
|
||||
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]);
|
||||
}
|
||||
|
||||
// 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;
|
||||
return new PageRequestResult(PageResult.TargetReached, query_result, stanzas_for_query);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -461,7 +430,7 @@ public class Dino.HistorySync {
|
|||
if (cancellable != null && cancellable.is_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) {
|
||||
|
|
|
@ -170,19 +170,21 @@ public class MessageProcessor : StreamInteractionModule, Object {
|
|||
|
||||
XmppStream? stream = stream_interactor.get_stream(account);
|
||||
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);
|
||||
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;
|
||||
}
|
||||
} 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)) ||
|
||||
(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) {
|
||||
new_message.server_id = Xep.UniqueStableStanzaIDs.get_stanza_id(message, new_message.counterpart.bare_jid);
|
||||
}
|
||||
} 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)) ||
|
||||
(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) {
|
||||
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) {
|
||||
bool is_mam_message = Xmpp.MessageArchiveManagement.MessageFlag.get_flag(stanza) != null;
|
||||
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)) {
|
||||
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);
|
||||
|
||||
long fallback_length = fallback.length;
|
||||
var fallback_location = new Xep.FallbackIndication.FallbackLocation(0, (int)fallback_length);
|
||||
var fallback_location = new Xep.FallbackIndication.FallbackLocation(0, (int)fallback.char_count());
|
||||
Xep.FallbackIndication.set_fallback(new_stanza, new Xep.FallbackIndication.Fallback(Xep.Replies.NS_URI, new Xep.FallbackIndication.FallbackLocation[] { fallback_location }));
|
||||
|
||||
return fallback;
|
||||
|
|
|
@ -72,7 +72,7 @@ public class MucManager : StreamInteractionModule, Object {
|
|||
|
||||
bool receive_history = true;
|
||||
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) {
|
||||
receive_history = false;
|
||||
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)
|
||||
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)) ||
|
||||
(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;
|
||||
|
||||
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;
|
||||
foreach (var fallback in message.get_fallbacks()) {
|
||||
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;
|
||||
|
|
|
@ -34,6 +34,7 @@ namespace Dino {
|
|||
if (self_word != null && (account.alias == null || account.alias.length == 0)) {
|
||||
return self_word;
|
||||
}
|
||||
if (account.alias != null && account.alias.length == 0) return null;
|
||||
return account.alias;
|
||||
}
|
||||
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_start">14</property>
|
||||
<property name="margin_end">14</property>
|
||||
<property name="wrap">True</property>
|
||||
<attributes>
|
||||
<attribute name="scale" value="0.8"></attribute>
|
||||
</attributes>
|
||||
|
|
|
@ -5,9 +5,10 @@ GenericName=Jabber/XMPP Client
|
|||
Keywords=chat;talk;im;message;xmpp;jabber;
|
||||
Exec=dino %U
|
||||
Icon=im.dino.Dino
|
||||
StartupNotify=false
|
||||
StartupNotify=true
|
||||
Terminal=false
|
||||
Type=Application
|
||||
Categories=GTK;Network;Chat;InstantMessaging;
|
||||
X-GNOME-UsesNotifications=true
|
||||
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;
|
||||
}
|
||||
|
||||
.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));
|
||||
border: 0;
|
||||
border-radius: 0;
|
||||
}
|
||||
|
||||
.dino-call-window .participant-header-bar button {
|
||||
background: none;
|
||||
}
|
||||
|
||||
.dino-call-window .participant-header-bar button:hover {
|
||||
.dino-call-window .participant-header-bar button:hover:not(.close) {
|
||||
background: rgba(255,255,255,0.15);
|
||||
border-color: rgba(255,255,255,0);
|
||||
box-shadow: none;
|
||||
|
|
|
@ -22,7 +22,12 @@ namespace Dino.Ui {
|
|||
private HashMap<string, ParticipantWidget> participant_widgets = new HashMap<string, ParticipantWidget>();
|
||||
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 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_height = 100;
|
||||
|
@ -54,9 +59,20 @@ namespace Dino.Ui {
|
|||
this.bind_property("controls-active", bottom_bar_revealer, "reveal-child", BindingFlags.SYNC_CREATE);
|
||||
|
||||
((Widget) this).add_controller(this_motion_events);
|
||||
this_motion_events.motion.connect(reveal_control_elements);
|
||||
this_motion_events.enter.connect(reveal_control_elements);
|
||||
this_motion_events.leave.connect(reveal_control_elements);
|
||||
this_motion_events.motion.connect((x, y) => {
|
||||
if ((latest_motion_x - x).abs() <= MOTION_RELEVANCE_THRESHOLD && (latest_motion_y - y).abs() <= MOTION_RELEVANCE_THRESHOLD) return;
|
||||
|
||||
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-height"].connect(reveal_control_elements);
|
||||
|
|
|
@ -74,14 +74,11 @@ namespace Dino.Ui {
|
|||
|
||||
header_bar.show_title_buttons = is_highest_row;
|
||||
if (is_highest_row) {
|
||||
header_bar.add_css_class("call-header-background");
|
||||
Gtk.Settings? gtk_settings = Gtk.Settings.get_default();
|
||||
if (gtk_settings != null) {
|
||||
string[] buttons = gtk_settings.gtk_decoration_layout.split(":");
|
||||
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();
|
||||
}
|
||||
|
|
|
@ -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 }) {
|
||||
// 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) {
|
||||
text_view.buffer.insert_at_cursor("\n", 1);
|
||||
} else if (text_view.buffer.text.strip() != "") {
|
||||
|
|
|
@ -39,6 +39,8 @@ public class View : Box {
|
|||
chooser.emoji_picked.connect((emoji) => {
|
||||
chat_text_view.text_view.buffer.insert_at_cursor(emoji, emoji.data.length);
|
||||
});
|
||||
chooser.closed.connect(do_focus);
|
||||
|
||||
emoji_button.set_popover(chooser);
|
||||
|
||||
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();
|
||||
main_wrap_box.add_controller(main_wrap_motion_events);
|
||||
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
|
||||
// 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.
|
||||
|
|
|
@ -225,7 +225,21 @@ public class ConversationSelectorRow : ListBoxRow {
|
|||
label.attributes = copy;
|
||||
}
|
||||
|
||||
private bool update_read_pending = false;
|
||||
private bool update_read_pending_force = 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);
|
||||
if (num_unread == current_num_unread && !force_update) return;
|
||||
num_unread = current_num_unread;
|
||||
|
|
|
@ -10,13 +10,16 @@ public class FileProvider : Dino.FileProvider, Object {
|
|||
|
||||
private StreamInteractor stream_interactor;
|
||||
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 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) {
|
||||
this.stream_interactor = stream_interactor;
|
||||
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));
|
||||
}
|
||||
|
||||
|
@ -114,8 +117,6 @@ public class FileProvider : Dino.FileProvider, Object {
|
|||
HttpFileReceiveData? http_receive_data = receive_data as HttpFileReceiveData;
|
||||
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);
|
||||
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;
|
||||
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);
|
||||
|
||||
try {
|
||||
|
|
|
@ -7,12 +7,15 @@ namespace Dino.Plugins.HttpFiles {
|
|||
public class HttpFileSender : FileSender, Object {
|
||||
private StreamInteractor stream_interactor;
|
||||
private Database db;
|
||||
private Soup.Session session;
|
||||
private HashMap<Account, long> max_file_sizes = new HashMap<Account, long>(Account.hash_func, Account.equals_func);
|
||||
|
||||
public HttpFileSender(StreamInteractor stream_interactor, Database db) {
|
||||
this.stream_interactor = stream_interactor;
|
||||
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.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);
|
||||
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);
|
||||
#if SOUP_3_0
|
||||
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 {
|
||||
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 (data.length >= 2 && data[1] >= 192 && data[1] < 224) {
|
||||
return srtp_session.decrypt_rtcp(data);
|
||||
|
@ -46,9 +50,12 @@ public class Handler {
|
|||
return srtp_session.decrypt_rtp(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;
|
||||
}
|
||||
|
||||
|
@ -79,7 +86,7 @@ public class Handler {
|
|||
err = private_key.generate(PKAlgorithm.ECDSA, 256);
|
||||
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);
|
||||
|
||||
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;
|
||||
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);
|
||||
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;
|
||||
|
@ -347,7 +352,7 @@ public class Dino.Plugins.Rtp.Device : MediaDevice, Object {
|
|||
if (media == "audio") {
|
||||
return Gst.Caps.from_string("audio/x-raw,rate=48000,channels=1");
|
||||
} else if (media == "video" && device.caps.get_size() > 0) {
|
||||
int best_index = 0;
|
||||
int best_index = -1;
|
||||
Value? best_fraction = null;
|
||||
int best_fps = 0;
|
||||
int best_width = 0;
|
||||
|
@ -390,10 +395,25 @@ public class Dino.Plugins.Rtp.Device : MediaDevice, Object {
|
|||
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);
|
||||
unowned Gst.Structure? that = res.get_structure(0);
|
||||
Value framerate = that.get_value("framerate");
|
||||
if (framerate.type() == typeof(Gst.ValueList)) {
|
||||
Value? framerate = that.get_value("framerate");
|
||||
if (framerate != null && framerate.type() == typeof(Gst.ValueList) && best_fraction != null) {
|
||||
that.set_value("framerate", best_fraction);
|
||||
}
|
||||
debug("Selected caps %s", res.to_string());
|
||||
|
|
|
@ -426,11 +426,11 @@ public class Dino.Plugins.Rtp.Plugin : RootInterface, VideoCallPlugin, Object {
|
|||
|
||||
if (media == "video") {
|
||||
// Pick best FPS
|
||||
int max_fps = 0;
|
||||
int max_fps = -1;
|
||||
Device? max_fps_device = null;
|
||||
foreach (Device device in devices) {
|
||||
int fps = get_max_fps(device);
|
||||
if (fps > max_fps) {
|
||||
if (fps > max_fps || max_fps_device == null) {
|
||||
max_fps = fps;
|
||||
max_fps_device = device;
|
||||
}
|
||||
|
|
|
@ -102,6 +102,9 @@ public class Dino.Plugins.Rtp.Stream : Xmpp.Xep.JingleRtp.Stream {
|
|||
send_rtp.drop = true;
|
||||
send_rtp.wait_on_eos = false;
|
||||
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);
|
||||
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) {
|
||||
if (sink == null) {
|
||||
debug("Sink is null");
|
||||
|
@ -323,6 +380,24 @@ public class Dino.Plugins.Rtp.Stream : Xmpp.Xep.JingleRtp.Stream {
|
|||
#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();
|
||||
|
||||
uint8[] data;
|
||||
|
@ -489,8 +564,8 @@ public class Dino.Plugins.Rtp.Stream : Xmpp.Xep.JingleRtp.Stream {
|
|||
}
|
||||
}
|
||||
|
||||
private uint16 previous_video_orientation_degree = uint16.MAX;
|
||||
public signal void video_orientation_changed(uint16 degree);
|
||||
private uint16 previous_incoming_video_orientation_degree = uint16.MAX;
|
||||
public signal void incoming_video_orientation_changed(uint16 degree);
|
||||
|
||||
public override void on_recv_rtp_data(Bytes bytes) {
|
||||
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 3: rotation_degree = 270; break;
|
||||
}
|
||||
if (rotation_degree != previous_video_orientation_degree) {
|
||||
video_orientation_changed(rotation_degree);
|
||||
previous_video_orientation_degree = rotation_degree;
|
||||
if (rotation_degree != previous_incoming_video_orientation_degree) {
|
||||
incoming_video_orientation_changed(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 Gst.Element output_tee;
|
||||
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) {
|
||||
base(plugin, content);
|
||||
|
@ -731,7 +806,7 @@ public class Dino.Plugins.Rtp.VideoStream : Stream {
|
|||
}
|
||||
|
||||
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();
|
||||
rotate = Gst.ElementFactory.make("videoflip", @"video_rotate_$rtpid");
|
||||
pipe.add(rotate);
|
||||
|
@ -780,7 +855,7 @@ public class Dino.Plugins.Rtp.VideoStream : Stream {
|
|||
output_tee.set_state(Gst.State.NULL);
|
||||
pipe.remove(output_tee);
|
||||
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) {
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
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);
|
||||
|
||||
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("height", out 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);
|
||||
return Source.REMOVE;
|
||||
});
|
||||
last_input_caps = caps;
|
||||
}
|
||||
|
||||
|
@ -226,9 +231,21 @@ public class Dino.Plugins.Rtp.VideoWidget : Gtk.Widget, Dino.Plugins.VideoCallWi
|
|||
if (connected_device == null) return;
|
||||
plugin.pause();
|
||||
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);
|
||||
#endif
|
||||
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);
|
||||
#if GST_1_20
|
||||
}
|
||||
#endif
|
||||
pipe.add(prepare);
|
||||
connected_device_element = connected_device.link_source();
|
||||
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());
|
||||
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) {
|
||||
message.add_flag(new MessageFlag(MessageFlag.TYPE_RECEIVED));
|
||||
} else if (sent_node != null) {
|
||||
|
|
|
@ -58,7 +58,7 @@ namespace Xmpp.MessageArchiveManagement.V2 {
|
|||
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) {
|
||||
|
@ -67,14 +67,14 @@ namespace Xmpp.MessageArchiveManagement.V2 {
|
|||
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) {
|
||||
var query_node = create_base_query(stream, mam_params);
|
||||
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 {
|
||||
|
||||
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 bool error { get; set; default=false; }
|
||||
public bool malformed { get; set; default=false; }
|
||||
public bool complete { get; set; default=false; }
|
||||
public string first { get; set; }
|
||||
public string last { get; set; }
|
||||
public string? first { get; set; }
|
||||
public string? last { get; set; }
|
||||
}
|
||||
|
||||
public class Module : XmppStreamModule {
|
||||
|
@ -36,45 +34,36 @@ public class Module : XmppStreamModule {
|
|||
|
||||
private async void query_availability(XmppStream stream) {
|
||||
Jid own_jid = stream.get_flag(Bind.Flag.IDENTITY).my_jid.bare_jid;
|
||||
|
||||
bool ver_2_available = yield stream.get_module(ServiceDiscovery.Module.IDENTITY).has_entity_feature(stream, own_jid, NS_URI);
|
||||
if (ver_2_available) {
|
||||
stream.add_flag(new Flag(NS_URI));
|
||||
bool mam_available = yield stream.get_module(ServiceDiscovery.Module.IDENTITY).has_entity_feature(stream, own_jid, NS_URI);
|
||||
if (mam_available) {
|
||||
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.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);
|
||||
|
||||
foreach (var field in fields) {
|
||||
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());
|
||||
if (queryid != null) {
|
||||
StanzaNode query_node = new StanzaNode.build("query", NS_URI).add_self_xmlns().put_node(data_form.get_submit_node());
|
||||
query_node.put_attribute("queryid", queryid);
|
||||
}
|
||||
return query_node;
|
||||
}
|
||||
|
||||
internal async QueryResult query_archive(XmppStream stream, string ns, Jid? mam_server, StanzaNode query_node, Cancellable? cancellable = null) {
|
||||
var res = new QueryResult();
|
||||
internal async QueryResult query_archive(XmppStream stream, Jid? mam_server, StanzaNode query_node, Cancellable? cancellable = null) {
|
||||
|
||||
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
|
||||
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);
|
||||
|
||||
// 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; }
|
||||
|
||||
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.last = rsm_node.get_deep_string_content("last");
|
||||
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;
|
||||
}
|
||||
|
@ -104,14 +98,36 @@ public class ReceivedPipelineListener : StanzaListener<MessageStanza> {
|
|||
public override string[] after_actions { get { return after_actions_const; } }
|
||||
|
||||
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) {
|
||||
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);
|
||||
string? mam_id = message.stanza.get_deep_attribute(NS_VER(stream) + ":result", NS_VER(stream) + ":id");
|
||||
string? query_id = message.stanza.get_deep_attribute(NS_VER(stream) + ":result", NS_VER(stream) + ":queryid");
|
||||
string? mam_id = message.stanza.get_deep_attribute(NS_URI + ":result", NS_URI + ":id");
|
||||
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.stanza = message_node;
|
||||
|
@ -124,10 +140,15 @@ public class ReceivedPipelineListener : StanzaListener<MessageStanza> {
|
|||
public class Flag : XmppStreamFlag {
|
||||
public static FlagIdentity<Flag> IDENTITY = new FlagIdentity<Flag>(NS_URI, "message_archive_management");
|
||||
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) {
|
||||
this.ns_ver = ns_ver;
|
||||
public static Flag get_flag(XmppStream stream) {
|
||||
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; }
|
||||
|
@ -155,8 +176,4 @@ public class MessageFlag : Xmpp.MessageFlag {
|
|||
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) {
|
||||
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);
|
||||
Flag? flag = stream.get_flag(Flag.IDENTITY);
|
||||
if (flag != null) {
|
||||
|
@ -77,6 +82,11 @@ public class Module : BookmarksProvider, XmppStreamModule {
|
|||
}
|
||||
|
||||
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 {
|
||||
Jid jid_parsed = new Jid(id);
|
||||
Flag? flag = stream.get_flag(Flag.IDENTITY);
|
||||
|
|
Loading…
Reference in a new issue