mirror of
https://github.com/dino/dino.git
synced 2025-07-04 00:02:11 -04:00
Compare commits
54 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
23301156ab | ||
|
6ec283ead2 | ||
|
7b9c0d3c4c | ||
|
b58627bfd8 | ||
|
3da0149061 | ||
|
7b21383eb8 | ||
|
6ef82b857d | ||
|
97054c45a3 | ||
|
c5e32eca14 | ||
|
7285977b85 | ||
|
585023c92e | ||
|
4113c324fe | ||
|
85e99a2bc8 | ||
|
17119ecc6d | ||
|
b1e0244e9a | ||
|
2bd0fc30fe | ||
|
39474d02e4 | ||
|
24876bee35 | ||
|
235efcdab9 | ||
|
08c18f884f | ||
|
a90edd0538 | ||
|
f4b7e661cf | ||
|
f39032bb94 | ||
|
7dc1657740 | ||
|
2d555c64ab | ||
|
b54582b8dd | ||
|
ed7bad4a73 | ||
|
cee0bfb22e | ||
|
b7b37e770e | ||
|
67cb8b4af0 | ||
|
f4e761cec4 | ||
|
df0013e3f4 | ||
|
0521f50146 | ||
|
92b7d97339 | ||
|
7c048ecaef | ||
|
da383a2fe9 | ||
|
3a925c4c00 | ||
|
0ed0495e0a | ||
|
569d4141cf | ||
|
f44ded88c8 | ||
|
a74b894147 | ||
|
f011adac37 | ||
|
58379acc9f | ||
|
2fb8d29b34 | ||
|
7a27634732 | ||
|
d155ec15d2 | ||
|
baf96d9d9f | ||
|
179c766d19 | ||
|
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)
|
|
||||||
|
|
@ -1,10 +1,38 @@
|
|||||||
include(PkgConfigWithFallback)
|
include(PkgConfigWithFallback)
|
||||||
|
|
||||||
find_pkg_config_with_fallback(WebRTCAudioProcessing
|
find_pkg_config_with_fallback(WebRTCAudioProcessing
|
||||||
|
PKG_CONFIG_NAME webrtc-audio-processing-1
|
||||||
|
LIB_NAMES webrtc_audio_processing-1
|
||||||
|
INCLUDE_NAMES modules/audio_processing/include/audio_processing.h
|
||||||
|
INCLUDE_DIR_SUFFIXES webrtc-audio-processing-1 webrtc_audio_processing-1
|
||||||
|
)
|
||||||
|
if(WebRTCAudioProcessing_FOUND AND NOT WebRTCAudioProcessing_VERSION)
|
||||||
|
set(WebRTCAudioProcessing_VERSION "1.0")
|
||||||
|
endif()
|
||||||
|
|
||||||
|
if(NOT WebRTCAudioProcessing_FOUND)
|
||||||
|
find_pkg_config_with_fallback(WebRTCAudioProcessing
|
||||||
PKG_CONFIG_NAME webrtc-audio-processing
|
PKG_CONFIG_NAME webrtc-audio-processing
|
||||||
LIB_NAMES webrtc_audio_processing
|
LIB_NAMES webrtc_audio_processing
|
||||||
INCLUDE_NAMES webrtc/modules/audio_processing/include/audio_processing.h
|
INCLUDE_NAMES webrtc/modules/audio_processing/include/audio_processing.h
|
||||||
INCLUDE_DIR_SUFFIXES webrtc-audio-processing webrtc_audio_processing
|
INCLUDE_DIR_SUFFIXES webrtc-audio-processing webrtc_audio_processing
|
||||||
)
|
)
|
||||||
|
if(WebRTCAudioProcessing_FOUND AND NOT WebRTCAudioProcessing_VERSION)
|
||||||
|
set(WebRTCAudioProcessing_VERSION "0.2")
|
||||||
|
endif()
|
||||||
|
endif(NOT WebRTCAudioProcessing_FOUND)
|
||||||
|
|
||||||
|
if(NOT WebRTCAudioProcessing_FOUND)
|
||||||
|
find_pkg_config_with_fallback(WebRTCAudioProcessing
|
||||||
|
PKG_CONFIG_NAME webrtc-audio-processing-2
|
||||||
|
LIB_NAMES webrtc_audio_processing-2
|
||||||
|
INCLUDE_NAMES modules/audio_processing/include/audio_processing.h
|
||||||
|
INCLUDE_DIR_SUFFIXES webrtc-audio-processing-2 webrtc_audio_processing-2
|
||||||
|
)
|
||||||
|
if(WebRTCAudioProcessing_FOUND AND NOT WebRTCAudioProcessing_VERSION)
|
||||||
|
set(WebRTCAudioProcessing_VERSION "2.0")
|
||||||
|
endif()
|
||||||
|
endif(NOT WebRTCAudioProcessing_FOUND)
|
||||||
|
|
||||||
include(FindPackageHandleStandardArgs)
|
include(FindPackageHandleStandardArgs)
|
||||||
find_package_handle_standard_args(WebRTCAudioProcessing
|
find_package_handle_standard_args(WebRTCAudioProcessing
|
||||||
|
@ -15,9 +15,15 @@ function(find_pkg_config_with_fallback name)
|
|||||||
|
|
||||||
# Try to find real file name of libraries
|
# Try to find real file name of libraries
|
||||||
foreach(lib ${${name}_PKG_CONFIG_LIBRARIES})
|
foreach(lib ${${name}_PKG_CONFIG_LIBRARIES})
|
||||||
|
if (${lib} STREQUAL "atomic")
|
||||||
|
set(${name}_${lib}_LIBRARY ${lib})
|
||||||
|
mark_as_advanced(${name}_${lib}_LIBRARY)
|
||||||
|
continue()
|
||||||
|
endif()
|
||||||
find_library(${name}_${lib}_LIBRARY ${lib} HINTS ${${name}_PKG_CONFIG_LIBRARY_DIRS})
|
find_library(${name}_${lib}_LIBRARY ${lib} HINTS ${${name}_PKG_CONFIG_LIBRARY_DIRS})
|
||||||
mark_as_advanced(${name}_${lib}_LIBRARY)
|
mark_as_advanced(${name}_${lib}_LIBRARY)
|
||||||
if(NOT ${name}_${lib}_LIBRARY)
|
if(NOT ${name}_${lib}_LIBRARY)
|
||||||
|
message(STATUS "Required library for package " ${name} " not found: " ${lib})
|
||||||
unset(${name}_FOUND)
|
unset(${name}_FOUND)
|
||||||
endif(NOT ${name}_${lib}_LIBRARY)
|
endif(NOT ${name}_${lib}_LIBRARY)
|
||||||
endforeach(lib)
|
endforeach(lib)
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
<Project>
|
<Project>
|
||||||
<name>Dino</name>
|
<name>Dino</name>
|
||||||
<short-name>dino</short-name>
|
<short-name>dino</short-name>
|
||||||
<shortdesc xml:lang="en">Modern XMPP Chat Client</shortdesc>
|
<shortdesc xml:lang="en">Modern XMPP chat client</shortdesc>
|
||||||
<shortdesc xml:lang="zh-TW">現代化的 XMPP 用戶端聊天軟件</shortdesc>
|
<shortdesc xml:lang="zh-TW">現代化的 XMPP 用戶端聊天軟件</shortdesc>
|
||||||
<shortdesc xml:lang="zh-CN">现代 XMPP 聊天客户端</shortdesc>
|
<shortdesc xml:lang="zh-CN">现代 XMPP 聊天客户端</shortdesc>
|
||||||
<shortdesc xml:lang="tr">Modern XMPP Sohbet İstemcisi</shortdesc>
|
<shortdesc xml:lang="tr">Modern XMPP Sohbet İstemcisi</shortdesc>
|
||||||
|
@ -9,7 +9,7 @@
|
|||||||
<name>Dino</name>
|
<name>Dino</name>
|
||||||
<short-name>dino</short-name>
|
<short-name>dino</short-name>
|
||||||
|
|
||||||
<shortdesc xml:lang="en">Modern XMPP Chat Client</shortdesc>
|
<shortdesc xml:lang="en">Modern XMPP chat client</shortdesc>
|
||||||
<description xml:lang="en">
|
<description xml:lang="en">
|
||||||
Dino is a modern open-source chat client for the desktop. It focuses on providing a clean and reliable Jabber/XMPP experience while having your privacy in mind.
|
Dino is a modern open-source chat client for the desktop. It focuses on providing a clean and reliable Jabber/XMPP experience while having your privacy in mind.
|
||||||
It supports end-to-end encryption with OMEMO and OpenPGP and allows configuring privacy-related features such as read receipts and typing notifications.
|
It supports end-to-end encryption with OMEMO and OpenPGP and allows configuring privacy-related features such as read receipts and typing notifications.
|
||||||
|
@ -40,12 +40,12 @@ public interface Application : GLib.Application {
|
|||||||
PresenceManager.start(stream_interactor);
|
PresenceManager.start(stream_interactor);
|
||||||
CounterpartInteractionManager.start(stream_interactor);
|
CounterpartInteractionManager.start(stream_interactor);
|
||||||
BlockingManager.start(stream_interactor);
|
BlockingManager.start(stream_interactor);
|
||||||
|
Calls.start(stream_interactor, db);
|
||||||
ConversationManager.start(stream_interactor, db);
|
ConversationManager.start(stream_interactor, db);
|
||||||
MucManager.start(stream_interactor);
|
MucManager.start(stream_interactor);
|
||||||
AvatarManager.start(stream_interactor, db);
|
AvatarManager.start(stream_interactor, db);
|
||||||
RosterManager.start(stream_interactor, db);
|
RosterManager.start(stream_interactor, db);
|
||||||
FileManager.start(stream_interactor, db);
|
FileManager.start(stream_interactor, db);
|
||||||
Calls.start(stream_interactor, db);
|
|
||||||
CallStore.start(stream_interactor, db);
|
CallStore.start(stream_interactor, db);
|
||||||
ContentItemStore.start(stream_interactor, db);
|
ContentItemStore.start(stream_interactor, db);
|
||||||
ChatInteraction.start(stream_interactor);
|
ChatInteraction.start(stream_interactor);
|
||||||
|
@ -202,6 +202,7 @@ public class Message : Object {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static uint hash_func(Message message) {
|
public static uint hash_func(Message message) {
|
||||||
|
if (message.body == null) return 0;
|
||||||
return message.body.hash();
|
return message.body.hash();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -61,8 +61,6 @@ namespace Dino {
|
|||||||
call_state.initiate_groupchat_call.begin(conversation.counterpart);
|
call_state.initiate_groupchat_call.begin(conversation.counterpart);
|
||||||
}
|
}
|
||||||
|
|
||||||
conversation.last_active = call.time;
|
|
||||||
|
|
||||||
call_outgoing(call, call_state, conversation);
|
call_outgoing(call, call_state, conversation);
|
||||||
|
|
||||||
return call_state;
|
return call_state;
|
||||||
@ -221,7 +219,6 @@ namespace Dino {
|
|||||||
|
|
||||||
Conversation conversation = stream_interactor.get_module(ConversationManager.IDENTITY).create_conversation(call.counterpart.bare_jid, account, Conversation.Type.CHAT);
|
Conversation conversation = stream_interactor.get_module(ConversationManager.IDENTITY).create_conversation(call.counterpart.bare_jid, account, Conversation.Type.CHAT);
|
||||||
stream_interactor.get_module(CallStore.IDENTITY).add_call(call, conversation);
|
stream_interactor.get_module(CallStore.IDENTITY).add_call(call, conversation);
|
||||||
conversation.last_active = call.time;
|
|
||||||
|
|
||||||
var call_state = new CallState(call, stream_interactor);
|
var call_state = new CallState(call, stream_interactor);
|
||||||
connect_call_state_signals(call_state);
|
connect_call_state_signals(call_state);
|
||||||
@ -294,7 +291,6 @@ namespace Dino {
|
|||||||
Conversation? conversation = stream_interactor.get_module(ConversationManager.IDENTITY).get_conversation(inviter_jid.bare_jid, account);
|
Conversation? conversation = stream_interactor.get_module(ConversationManager.IDENTITY).get_conversation(inviter_jid.bare_jid, account);
|
||||||
if (conversation == null) return null;
|
if (conversation == null) return null;
|
||||||
stream_interactor.get_module(CallStore.IDENTITY).add_call(call, conversation);
|
stream_interactor.get_module(CallStore.IDENTITY).add_call(call, conversation);
|
||||||
conversation.last_active = call.time;
|
|
||||||
|
|
||||||
CallState call_state = new CallState(call, stream_interactor);
|
CallState call_state = new CallState(call, stream_interactor);
|
||||||
connect_call_state_signals(call_state);
|
connect_call_state_signals(call_state);
|
||||||
@ -465,7 +461,6 @@ namespace Dino {
|
|||||||
|
|
||||||
Conversation? conversation = stream_interactor.get_module(ConversationManager.IDENTITY).approx_conversation_for_stanza(from_jid, to_jid, account, message_stanza.type_);
|
Conversation? conversation = stream_interactor.get_module(ConversationManager.IDENTITY).approx_conversation_for_stanza(from_jid, to_jid, account, message_stanza.type_);
|
||||||
if (conversation == null) return;
|
if (conversation == null) return;
|
||||||
conversation.last_active = call_state.call.time;
|
|
||||||
|
|
||||||
if (call_state.call.direction == Call.DIRECTION_INCOMING) {
|
if (call_state.call.direction == Call.DIRECTION_INCOMING) {
|
||||||
call_incoming(call_state.call, call_state, conversation, video_requested, multiparty);
|
call_incoming(call_state.call, call_state, conversation, video_requested, multiparty);
|
||||||
|
@ -29,6 +29,8 @@ public class ConversationManager : StreamInteractionModule, Object {
|
|||||||
stream_interactor.account_removed.connect(on_account_removed);
|
stream_interactor.account_removed.connect(on_account_removed);
|
||||||
stream_interactor.get_module(MessageProcessor.IDENTITY).received_pipeline.connect(new MessageListener(stream_interactor));
|
stream_interactor.get_module(MessageProcessor.IDENTITY).received_pipeline.connect(new MessageListener(stream_interactor));
|
||||||
stream_interactor.get_module(MessageProcessor.IDENTITY).message_sent.connect(handle_sent_message);
|
stream_interactor.get_module(MessageProcessor.IDENTITY).message_sent.connect(handle_sent_message);
|
||||||
|
stream_interactor.get_module(Calls.IDENTITY).call_incoming.connect(handle_new_call);
|
||||||
|
stream_interactor.get_module(Calls.IDENTITY).call_outgoing.connect(handle_new_call);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Conversation create_conversation(Jid jid, Account account, Conversation.Type? type = null) {
|
public Conversation create_conversation(Jid jid, Account account, Conversation.Type? type = null) {
|
||||||
@ -194,6 +196,11 @@ public class ConversationManager : StreamInteractionModule, Object {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void handle_new_call(Call call, CallState state, Conversation conversation) {
|
||||||
|
conversation.last_active = call.time;
|
||||||
|
start_conversation(conversation);
|
||||||
|
}
|
||||||
|
|
||||||
private void add_conversation(Conversation conversation) {
|
private void add_conversation(Conversation conversation) {
|
||||||
if (!conversations[conversation.account].has_key(conversation.counterpart)) {
|
if (!conversations[conversation.account].has_key(conversation.counterpart)) {
|
||||||
conversations[conversation.account][conversation.counterpart] = new ArrayList<Conversation>(Conversation.equals_func);
|
conversations[conversation.account][conversation.counterpart] = new ArrayList<Conversation>(Conversation.equals_func);
|
||||||
|
@ -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;
|
||||||
|
@ -54,6 +54,7 @@ public class MucManager : StreamInteractionModule, Object {
|
|||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
});
|
});
|
||||||
|
stream_interactor.get_module(MessageProcessor.IDENTITY).build_message_stanza.connect(on_build_message_stanza);
|
||||||
}
|
}
|
||||||
|
|
||||||
// already_autojoin: Without this flag we'd be retrieving bookmarks (to check for autojoin) from the sender on every join
|
// already_autojoin: Without this flag we'd be retrieving bookmarks (to check for autojoin) from the sender on every join
|
||||||
@ -72,7 +73,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;
|
||||||
@ -83,11 +84,6 @@ public class MucManager : StreamInteractionModule, Object {
|
|||||||
}
|
}
|
||||||
mucs_joining[account].add(jid);
|
mucs_joining[account].add(jid);
|
||||||
|
|
||||||
if (!mucs_todo.has_key(account)) {
|
|
||||||
mucs_todo[account] = new HashSet<Jid>(Jid.hash_bare_func, Jid.equals_bare_func);
|
|
||||||
}
|
|
||||||
mucs_todo[account].add(jid.with_resource(nick_));
|
|
||||||
|
|
||||||
Muc.JoinResult? res = yield stream.get_module(Xep.Muc.Module.IDENTITY).enter(stream, jid.bare_jid, nick_, password, history_since, receive_history, null);
|
Muc.JoinResult? res = yield stream.get_module(Xep.Muc.Module.IDENTITY).enter(stream, jid.bare_jid, nick_, password, history_since, receive_history, null);
|
||||||
|
|
||||||
mucs_joining[account].remove(jid);
|
mucs_joining[account].remove(jid);
|
||||||
@ -126,6 +122,11 @@ public class MucManager : StreamInteractionModule, Object {
|
|||||||
enter_errors[jid] = res.muc_error;
|
enter_errors[jid] = res.muc_error;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!mucs_todo.has_key(account)) {
|
||||||
|
mucs_todo[account] = new HashSet<Jid>(Jid.hash_bare_func, Jid.equals_bare_func);
|
||||||
|
}
|
||||||
|
mucs_todo[account].add(jid.with_resource(res.nick ?? nick_));
|
||||||
|
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -651,6 +652,12 @@ public class MucManager : StreamInteractionModule, Object {
|
|||||||
conference_removed(account, jid);
|
conference_removed(account, jid);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void on_build_message_stanza(Entities.Message message, Xmpp.MessageStanza message_stanza, Conversation conversation) {
|
||||||
|
if (conversation.type_ == Conversation.Type.GROUPCHAT_PM) {
|
||||||
|
Xmpp.Xep.Muc.add_muc_pm_message_stanza_x_node(message_stanza);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void self_ping(Account account) {
|
private void self_ping(Account account) {
|
||||||
XmppStream? stream = stream_interactor.get_stream(account);
|
XmppStream? stream = stream_interactor.get_stream(account);
|
||||||
if (stream == null) return;
|
if (stream == null) return;
|
||||||
|
@ -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);
|
||||||
|
@ -232,6 +232,7 @@ add_executable(dino ${MAIN_VALA_C} ${MAIN_GRESOURCES_TARGET})
|
|||||||
add_dependencies(dino ${GETTEXT_PACKAGE}-translations)
|
add_dependencies(dino ${GETTEXT_PACKAGE}-translations)
|
||||||
target_include_directories(dino PRIVATE src)
|
target_include_directories(dino PRIVATE src)
|
||||||
target_link_libraries(dino libdino ${MAIN_PACKAGES})
|
target_link_libraries(dino libdino ${MAIN_PACKAGES})
|
||||||
|
set_target_properties(dino PROPERTIES ENABLE_EXPORTS TRUE)
|
||||||
|
|
||||||
if(WIN32)
|
if(WIN32)
|
||||||
target_link_libraries(dino -mwindows)
|
target_link_libraries(dino -mwindows)
|
||||||
|
@ -7,8 +7,8 @@
|
|||||||
</style>
|
</style>
|
||||||
<child>
|
<child>
|
||||||
<object class="DinoUiSizingBin">
|
<object class="DinoUiSizingBin">
|
||||||
<property name="target-width">350</property>
|
<property name="target-width">400</property>
|
||||||
<property name="max-width">350</property>
|
<property name="max-width">400</property>
|
||||||
<property name="hexpand">True</property>
|
<property name="hexpand">True</property>
|
||||||
<child>
|
<child>
|
||||||
<object class="GtkBox">
|
<object class="GtkBox">
|
||||||
|
@ -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>
|
||||||
|
@ -14,8 +14,6 @@
|
|||||||
<property name="valign">end</property>
|
<property name="valign">end</property>
|
||||||
<child>
|
<child>
|
||||||
<object class="GtkOverlay">
|
<object class="GtkOverlay">
|
||||||
<child>
|
|
||||||
<object class="GtkBox" id="main_event_box">
|
|
||||||
<child>
|
<child>
|
||||||
<object class="DinoUiSizeRequestBox" id="main">
|
<object class="DinoUiSizeRequestBox" id="main">
|
||||||
<property name="margin-bottom">15</property>
|
<property name="margin-bottom">15</property>
|
||||||
@ -23,8 +21,6 @@
|
|||||||
<property name="size-request-mode">height-for-width</property>
|
<property name="size-request-mode">height-for-width</property>
|
||||||
</object>
|
</object>
|
||||||
</child>
|
</child>
|
||||||
</object>
|
|
||||||
</child>
|
|
||||||
<child type="overlay">
|
<child type="overlay">
|
||||||
<object class="GtkBox" id="message_menu_box">
|
<object class="GtkBox" id="message_menu_box">
|
||||||
<property name="margin-end">10</property>
|
<property name="margin-end">10</property>
|
||||||
|
@ -8,8 +8,8 @@
|
|||||||
</style>
|
</style>
|
||||||
<child>
|
<child>
|
||||||
<object class="DinoUiSizingBin">
|
<object class="DinoUiSizingBin">
|
||||||
<property name="target-width">500</property>
|
<property name="target-width">400</property>
|
||||||
<property name="max-width">500</property>
|
<property name="max-width">400</property>
|
||||||
<property name="hexpand">True</property>
|
<property name="hexpand">True</property>
|
||||||
<child>
|
<child>
|
||||||
<object class="GtkBox">
|
<object class="GtkBox">
|
||||||
|
@ -1,11 +1,9 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<component type="desktop">
|
<component type="desktop">
|
||||||
<id>im.dino.Dino</id>
|
<id>im.dino.Dino</id>
|
||||||
<launchable type="desktop-id">im.dino.Dino.desktop</launchable>
|
|
||||||
<metadata_license>CC0-1.0</metadata_license>
|
<metadata_license>CC0-1.0</metadata_license>
|
||||||
<project_license>GPL-3.0+</project_license>
|
|
||||||
<name>Dino</name>
|
<name>Dino</name>
|
||||||
<summary>Modern XMPP Chat Client</summary>
|
<summary>Modern XMPP chat client</summary>
|
||||||
<summary xml:lang="zh_TW">現代化的 XMPP 用戶端聊天軟件</summary>
|
<summary xml:lang="zh_TW">現代化的 XMPP 用戶端聊天軟件</summary>
|
||||||
<summary xml:lang="zh_CN">现代 XMPP 聊天客户端</summary>
|
<summary xml:lang="zh_CN">现代 XMPP 聊天客户端</summary>
|
||||||
<summary xml:lang="tr">Modern XMPP Sohbet İstemcisi</summary>
|
<summary xml:lang="tr">Modern XMPP Sohbet İstemcisi</summary>
|
||||||
@ -39,6 +37,8 @@
|
|||||||
<summary xml:lang="cs">Moderní XMPP klient</summary>
|
<summary xml:lang="cs">Moderní XMPP klient</summary>
|
||||||
<summary xml:lang="ca">Client de xat XMPP modern</summary>
|
<summary xml:lang="ca">Client de xat XMPP modern</summary>
|
||||||
<summary xml:lang="ar">تطبيق حديث للدردشة عبر XMPP</summary>
|
<summary xml:lang="ar">تطبيق حديث للدردشة عبر XMPP</summary>
|
||||||
|
<icon type="stock">im.dino.Dino</icon>
|
||||||
|
<icon type="remote" width="128" height="128">https://dino.im/img/appdata/icon-dino-0.4-128x128.png</icon>
|
||||||
<description>
|
<description>
|
||||||
<p>Dino is a modern open-source chat client for the desktop. It focuses on providing a clean and reliable Jabber/XMPP experience while having your privacy in mind.</p>
|
<p>Dino is a modern open-source chat client for the desktop. It focuses on providing a clean and reliable Jabber/XMPP experience while having your privacy in mind.</p>
|
||||||
<p xml:lang="zh_TW">Dino 是一個爲桌面打造的現代化開放原始碼用戶端聊天軟件。它致力於提供一份簡洁而可靠的 Jabber/XMPP 體驗,同時亦尊重您的私隱。</p>
|
<p xml:lang="zh_TW">Dino 是一個爲桌面打造的現代化開放原始碼用戶端聊天軟件。它致力於提供一份簡洁而可靠的 Jabber/XMPP 體驗,同時亦尊重您的私隱。</p>
|
||||||
@ -146,24 +146,22 @@
|
|||||||
<p xml:lang="ca">Dino recupera l'historial del servidor i sincronitza els missatges amb altres dispositius.</p>
|
<p xml:lang="ca">Dino recupera l'historial del servidor i sincronitza els missatges amb altres dispositius.</p>
|
||||||
<p xml:lang="ar">يقوم Dino بجلب السِجلّ مِن السيرفر ثم يُزامِن الرسائل مع الأجهزة الأخرى.</p>
|
<p xml:lang="ar">يقوم Dino بجلب السِجلّ مِن السيرفر ثم يُزامِن الرسائل مع الأجهزة الأخرى.</p>
|
||||||
</description>
|
</description>
|
||||||
<screenshots>
|
<categories>
|
||||||
<screenshot type="default">
|
<category>Network</category>
|
||||||
<image>https://dino.im/img/appdata/2022-02_screenshot-main.png</image>
|
<category>InstantMessaging</category>
|
||||||
</screenshot>
|
<category>Chat</category>
|
||||||
<screenshot>
|
<category>GTK</category>
|
||||||
<image>https://dino.im/img/appdata/2022-02_screenshot-call.png</image>
|
</categories>
|
||||||
</screenshot>
|
<keywords>
|
||||||
<screenshot>
|
<keyword translate="no">Jabber</keyword>
|
||||||
<image>https://dino.im/img/appdata/start_chat.png</image>
|
<keyword translate="no">XMPP</keyword>
|
||||||
</screenshot>
|
</keywords>
|
||||||
</screenshots>
|
|
||||||
<translation type="gettext">dino</translation>
|
|
||||||
<developer_name>Dino Development Team</developer_name>
|
|
||||||
<url type="homepage">https://dino.im</url>
|
<url type="homepage">https://dino.im</url>
|
||||||
<url type="bugtracker">https://github.com/dino/dino/issues</url>
|
<url type="bugtracker">https://github.com/dino/dino/issues</url>
|
||||||
<url type="donation">https://dino.im/#donate</url>
|
<url type="donation">https://dino.im/#donate</url>
|
||||||
|
<url type="vcs-browser">https://github.com/dino/dino</url>
|
||||||
<url type="translate">https://hosted.weblate.org/projects/dino/</url>
|
<url type="translate">https://hosted.weblate.org/projects/dino/</url>
|
||||||
<update_contact>appstream@dino.im</update_contact>
|
<launchable type="desktop-id">im.dino.Dino.desktop</launchable>
|
||||||
<releases>
|
<releases>
|
||||||
<release date="2023-02-07" version="0.4">
|
<release date="2023-02-07" version="0.4">
|
||||||
<description>
|
<description>
|
||||||
@ -186,8 +184,50 @@
|
|||||||
</description>
|
</description>
|
||||||
</release>
|
</release>
|
||||||
</releases>
|
</releases>
|
||||||
|
<provides>
|
||||||
|
<mediatype>x-scheme-handler/xmpp</mediatype>
|
||||||
|
</provides>
|
||||||
|
<recommends>
|
||||||
|
<internet>always</internet>
|
||||||
|
</recommends>
|
||||||
|
<supports>
|
||||||
|
<control>pointing</control>
|
||||||
|
<control>keyboard</control>
|
||||||
|
<control>touch</control>
|
||||||
|
</supports>
|
||||||
|
<project_license>GPL-3.0+</project_license>
|
||||||
|
<developer id="im.dino">
|
||||||
|
<name>Dino Development Team</name>
|
||||||
|
<url>https://dino.im/</url>
|
||||||
|
</developer>
|
||||||
|
<!-- Deprecated: -->
|
||||||
|
<developer_name>Dino Development Team</developer_name>
|
||||||
|
<screenshots>
|
||||||
|
<screenshot type="default">
|
||||||
|
<image type="source" width="2244" height="1644" scale="2">https://dino.im/img/appdata/screenshot-dino-0.4-main-2244x1644@2.png</image>
|
||||||
|
<caption>Main screen</caption>
|
||||||
|
</screenshot>
|
||||||
|
<screenshot>
|
||||||
|
<image type="source" width="1441" height="929" scale="2">https://dino.im/img/appdata/screenshot-dino-0.4-call-1441x929@2.png</image>
|
||||||
|
<caption>Video call screen</caption>
|
||||||
|
</screenshot>
|
||||||
|
<screenshot>
|
||||||
|
<image type="source" width="2244" height="1644" scale="2">https://dino.im/img/appdata/screenshot-dino-0.4-search-2244x1644@2.png</image>
|
||||||
|
<caption>Main screen featuring search overlay</caption>
|
||||||
|
</screenshot>
|
||||||
|
<screenshot>
|
||||||
|
<image type="source" width="964" height="1552" scale="2">https://dino.im/img/appdata/screenshot-dino-0.4-mobile-964x1552@2.png</image>
|
||||||
|
<caption>Main screen in mobile size window</caption>
|
||||||
|
</screenshot>
|
||||||
|
</screenshots>
|
||||||
|
<translation type="gettext">dino</translation>
|
||||||
<content_rating type="oars-1.1">
|
<content_rating type="oars-1.1">
|
||||||
<content_attribute id="social-chat">intense</content_attribute>
|
<content_attribute id="social-chat">intense</content_attribute>
|
||||||
<content_attribute id="social-audio">intense</content_attribute>
|
<content_attribute id="social-audio">intense</content_attribute>
|
||||||
</content_rating>
|
</content_rating>
|
||||||
|
<update_contact>appstream@dino.im</update_contact>
|
||||||
|
<branding>
|
||||||
|
<color type="primary" scheme_preference="light">#B2DFDB</color>
|
||||||
|
<color type="primary" scheme_preference="dark">#004D40</color>
|
||||||
|
</branding>
|
||||||
</component>
|
</component>
|
||||||
|
@ -1,34 +1,32 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<component type="desktop">
|
<component type="desktop">
|
||||||
<id>im.dino.Dino</id>
|
<id>im.dino.Dino</id>
|
||||||
<launchable type="desktop-id">im.dino.Dino.desktop</launchable>
|
|
||||||
<metadata_license>CC0-1.0</metadata_license>
|
<metadata_license>CC0-1.0</metadata_license>
|
||||||
<project_license>GPL-3.0+</project_license>
|
|
||||||
<name>Dino</name>
|
<name>Dino</name>
|
||||||
<summary>Modern XMPP Chat Client</summary>
|
<summary>Modern XMPP chat client</summary>
|
||||||
|
<icon type="stock">im.dino.Dino</icon>
|
||||||
|
<icon type="remote" width="128" height="128">https://dino.im/img/appdata/icon-dino-0.4-128x128.png</icon>
|
||||||
<description>
|
<description>
|
||||||
<p>Dino is a modern open-source chat client for the desktop. It focuses on providing a clean and reliable Jabber/XMPP experience while having your privacy in mind.</p>
|
<p>Dino is a modern open-source chat client for the desktop. It focuses on providing a clean and reliable Jabber/XMPP experience while having your privacy in mind.</p>
|
||||||
<p>It supports end-to-end encryption with OMEMO and OpenPGP and allows configuring privacy-related features such as read receipts and typing notifications.</p>
|
<p>It supports end-to-end encryption with OMEMO and OpenPGP and allows configuring privacy-related features such as read receipts and typing notifications.</p>
|
||||||
<p>Dino fetches history from the server and synchronizes messages with other devices.</p>
|
<p>Dino fetches history from the server and synchronizes messages with other devices.</p>
|
||||||
</description>
|
</description>
|
||||||
<screenshots>
|
<categories>
|
||||||
<screenshot type="default">
|
<category>Network</category>
|
||||||
<image>https://dino.im/img/appdata/2022-02_screenshot-main.png</image>
|
<category>InstantMessaging</category>
|
||||||
</screenshot>
|
<category>Chat</category>
|
||||||
<screenshot>
|
<category>GTK</category>
|
||||||
<image>https://dino.im/img/appdata/2022-02_screenshot-call.png</image>
|
</categories>
|
||||||
</screenshot>
|
<keywords>
|
||||||
<screenshot>
|
<keyword translate="no">Jabber</keyword>
|
||||||
<image>https://dino.im/img/appdata/start_chat.png</image>
|
<keyword translate="no">XMPP</keyword>
|
||||||
</screenshot>
|
</keywords>
|
||||||
</screenshots>
|
|
||||||
<translation type="gettext">dino</translation>
|
|
||||||
<developer_name>Dino Development Team</developer_name>
|
|
||||||
<url type="homepage">https://dino.im</url>
|
<url type="homepage">https://dino.im</url>
|
||||||
<url type="bugtracker">https://github.com/dino/dino/issues</url>
|
<url type="bugtracker">https://github.com/dino/dino/issues</url>
|
||||||
<url type="donation">https://dino.im/#donate</url>
|
<url type="donation">https://dino.im/#donate</url>
|
||||||
|
<url type="vcs-browser">https://github.com/dino/dino</url>
|
||||||
<url type="translate">https://hosted.weblate.org/projects/dino/</url>
|
<url type="translate">https://hosted.weblate.org/projects/dino/</url>
|
||||||
<update_contact>appstream@dino.im</update_contact>
|
<launchable type="desktop-id">im.dino.Dino.desktop</launchable>
|
||||||
<releases>
|
<releases>
|
||||||
<release date="2023-02-07" version="0.4">
|
<release date="2023-02-07" version="0.4">
|
||||||
<description>
|
<description>
|
||||||
@ -51,8 +49,50 @@
|
|||||||
</description>
|
</description>
|
||||||
</release>
|
</release>
|
||||||
</releases>
|
</releases>
|
||||||
|
<provides>
|
||||||
|
<mediatype>x-scheme-handler/xmpp</mediatype>
|
||||||
|
</provides>
|
||||||
|
<recommends>
|
||||||
|
<internet>always</internet>
|
||||||
|
</recommends>
|
||||||
|
<supports>
|
||||||
|
<control>pointing</control>
|
||||||
|
<control>keyboard</control>
|
||||||
|
<control>touch</control>
|
||||||
|
</supports>
|
||||||
|
<project_license>GPL-3.0+</project_license>
|
||||||
|
<developer id="im.dino">
|
||||||
|
<name>Dino Development Team</name>
|
||||||
|
<url>https://dino.im/</url>
|
||||||
|
</developer>
|
||||||
|
<!-- Deprecated: -->
|
||||||
|
<developer_name>Dino Development Team</developer_name>
|
||||||
|
<screenshots>
|
||||||
|
<screenshot type="default">
|
||||||
|
<image type="source" width="2244" height="1644" scale="2">https://dino.im/img/appdata/screenshot-dino-0.4-main-2244x1644@2.png</image>
|
||||||
|
<caption>Main screen</caption>
|
||||||
|
</screenshot>
|
||||||
|
<screenshot>
|
||||||
|
<image type="source" width="1441" height="929" scale="2">https://dino.im/img/appdata/screenshot-dino-0.4-call-1441x929@2.png</image>
|
||||||
|
<caption>Video call screen</caption>
|
||||||
|
</screenshot>
|
||||||
|
<screenshot>
|
||||||
|
<image type="source" width="2244" height="1644" scale="2">https://dino.im/img/appdata/screenshot-dino-0.4-search-2244x1644@2.png</image>
|
||||||
|
<caption>Main screen featuring search overlay</caption>
|
||||||
|
</screenshot>
|
||||||
|
<screenshot>
|
||||||
|
<image type="source" width="964" height="1552" scale="2">https://dino.im/img/appdata/screenshot-dino-0.4-mobile-964x1552@2.png</image>
|
||||||
|
<caption>Main screen in mobile size window</caption>
|
||||||
|
</screenshot>
|
||||||
|
</screenshots>
|
||||||
|
<translation type="gettext">dino</translation>
|
||||||
<content_rating type="oars-1.1">
|
<content_rating type="oars-1.1">
|
||||||
<content_attribute id="social-chat">intense</content_attribute>
|
<content_attribute id="social-chat">intense</content_attribute>
|
||||||
<content_attribute id="social-audio">intense</content_attribute>
|
<content_attribute id="social-audio">intense</content_attribute>
|
||||||
</content_rating>
|
</content_rating>
|
||||||
|
<update_contact>appstream@dino.im</update_contact>
|
||||||
|
<branding>
|
||||||
|
<color type="primary" scheme_preference="light">#B2DFDB</color>
|
||||||
|
<color type="primary" scheme_preference="dark">#004D40</color>
|
||||||
|
</branding>
|
||||||
</component>
|
</component>
|
||||||
|
@ -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;
|
||||||
|
@ -103,6 +103,9 @@
|
|||||||
<property name="hexpand">false</property>
|
<property name="hexpand">false</property>
|
||||||
<property name="maximum-size">400</property>
|
<property name="maximum-size">400</property>
|
||||||
<property name="tightening-threshold">400</property>
|
<property name="tightening-threshold">400</property>
|
||||||
|
<child>
|
||||||
|
<object class="DinoUiNaturalSizeIncrease">
|
||||||
|
<property name="min-natural-width">400</property>
|
||||||
<child>
|
<child>
|
||||||
<object class="AdwBin" id="search_frame">
|
<object class="AdwBin" id="search_frame">
|
||||||
<property name="hexpand">true</property>
|
<property name="hexpand">true</property>
|
||||||
@ -118,4 +121,6 @@
|
|||||||
</object>
|
</object>
|
||||||
</child>
|
</child>
|
||||||
</object>
|
</object>
|
||||||
|
</child>
|
||||||
|
</object>
|
||||||
</interface>
|
</interface>
|
||||||
|
@ -1052,7 +1052,7 @@ msgid "Check spelling"
|
|||||||
msgstr "التدقيق الإملائي"
|
msgstr "التدقيق الإملائي"
|
||||||
|
|
||||||
#: main/data/im.dino.Dino.appdata.xml.in:7
|
#: main/data/im.dino.Dino.appdata.xml.in:7
|
||||||
msgid "Modern XMPP Chat Client"
|
msgid "Modern XMPP chat client"
|
||||||
msgstr "تطبيق حديث للدردشة عبر XMPP"
|
msgstr "تطبيق حديث للدردشة عبر XMPP"
|
||||||
|
|
||||||
#: main/data/im.dino.Dino.appdata.xml.in:9
|
#: main/data/im.dino.Dino.appdata.xml.in:9
|
||||||
|
@ -1035,7 +1035,7 @@ msgid "Check spelling"
|
|||||||
msgstr "Revisa l'ortografia"
|
msgstr "Revisa l'ortografia"
|
||||||
|
|
||||||
#: main/data/im.dino.Dino.appdata.xml.in:7
|
#: main/data/im.dino.Dino.appdata.xml.in:7
|
||||||
msgid "Modern XMPP Chat Client"
|
msgid "Modern XMPP chat client"
|
||||||
msgstr "Client de xat XMPP modern"
|
msgstr "Client de xat XMPP modern"
|
||||||
|
|
||||||
#: main/data/im.dino.Dino.appdata.xml.in:9
|
#: main/data/im.dino.Dino.appdata.xml.in:9
|
||||||
|
@ -1038,7 +1038,7 @@ msgid "Check spelling"
|
|||||||
msgstr "Kontrola pravopisu"
|
msgstr "Kontrola pravopisu"
|
||||||
|
|
||||||
#: main/data/im.dino.Dino.appdata.xml.in:7
|
#: main/data/im.dino.Dino.appdata.xml.in:7
|
||||||
msgid "Modern XMPP Chat Client"
|
msgid "Modern XMPP chat client"
|
||||||
msgstr "Moderní XMPP klient"
|
msgstr "Moderní XMPP klient"
|
||||||
|
|
||||||
#: main/data/im.dino.Dino.appdata.xml.in:9
|
#: main/data/im.dino.Dino.appdata.xml.in:9
|
||||||
|
@ -1034,7 +1034,7 @@ msgid "Check spelling"
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: main/data/im.dino.Dino.appdata.xml.in:7
|
#: main/data/im.dino.Dino.appdata.xml.in:7
|
||||||
msgid "Modern XMPP Chat Client"
|
msgid "Modern XMPP chat client"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: main/data/im.dino.Dino.appdata.xml.in:9
|
#: main/data/im.dino.Dino.appdata.xml.in:9
|
||||||
|
@ -1033,7 +1033,7 @@ msgid "Check spelling"
|
|||||||
msgstr "Rechtschreibung prüfen"
|
msgstr "Rechtschreibung prüfen"
|
||||||
|
|
||||||
#: main/data/im.dino.Dino.appdata.xml.in:7
|
#: main/data/im.dino.Dino.appdata.xml.in:7
|
||||||
msgid "Modern XMPP Chat Client"
|
msgid "Modern XMPP chat client"
|
||||||
msgstr "Modernes XMPP-Chat-Programm"
|
msgstr "Modernes XMPP-Chat-Programm"
|
||||||
|
|
||||||
#: main/data/im.dino.Dino.appdata.xml.in:9
|
#: main/data/im.dino.Dino.appdata.xml.in:9
|
||||||
|
@ -1035,7 +1035,7 @@ msgid "Check spelling"
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: main/data/im.dino.Dino.appdata.xml.in:7
|
#: main/data/im.dino.Dino.appdata.xml.in:7
|
||||||
msgid "Modern XMPP Chat Client"
|
msgid "Modern XMPP chat client"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: main/data/im.dino.Dino.appdata.xml.in:9
|
#: main/data/im.dino.Dino.appdata.xml.in:9
|
||||||
|
@ -1040,7 +1040,7 @@ msgid "Check spelling"
|
|||||||
msgstr "Ορθογραφικός έλεγχος"
|
msgstr "Ορθογραφικός έλεγχος"
|
||||||
|
|
||||||
#: main/data/im.dino.Dino.appdata.xml.in:7
|
#: main/data/im.dino.Dino.appdata.xml.in:7
|
||||||
msgid "Modern XMPP Chat Client"
|
msgid "Modern XMPP chat client"
|
||||||
msgstr "Σύγχρονος XMPP Chat Client"
|
msgstr "Σύγχρονος XMPP Chat Client"
|
||||||
|
|
||||||
#: main/data/im.dino.Dino.appdata.xml.in:9
|
#: main/data/im.dino.Dino.appdata.xml.in:9
|
||||||
|
@ -1025,7 +1025,7 @@ msgid "Check spelling"
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: main/data/im.dino.Dino.appdata.xml.in:7
|
#: main/data/im.dino.Dino.appdata.xml.in:7
|
||||||
msgid "Modern XMPP Chat Client"
|
msgid "Modern XMPP chat client"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: main/data/im.dino.Dino.appdata.xml.in:9
|
#: main/data/im.dino.Dino.appdata.xml.in:9
|
||||||
|
@ -1035,7 +1035,7 @@ msgid "Check spelling"
|
|||||||
msgstr "Kontroli ortografion"
|
msgstr "Kontroli ortografion"
|
||||||
|
|
||||||
#: main/data/im.dino.Dino.appdata.xml.in:7
|
#: main/data/im.dino.Dino.appdata.xml.in:7
|
||||||
msgid "Modern XMPP Chat Client"
|
msgid "Modern XMPP chat client"
|
||||||
msgstr "Moderna XMPP-Retebabililo"
|
msgstr "Moderna XMPP-Retebabililo"
|
||||||
|
|
||||||
#: main/data/im.dino.Dino.appdata.xml.in:9
|
#: main/data/im.dino.Dino.appdata.xml.in:9
|
||||||
|
@ -1038,7 +1038,7 @@ msgid "Check spelling"
|
|||||||
msgstr "Comprobar ortografía"
|
msgstr "Comprobar ortografía"
|
||||||
|
|
||||||
#: main/data/im.dino.Dino.appdata.xml.in:7
|
#: main/data/im.dino.Dino.appdata.xml.in:7
|
||||||
msgid "Modern XMPP Chat Client"
|
msgid "Modern XMPP chat client"
|
||||||
msgstr "Un cliente de XMPP moderno"
|
msgstr "Un cliente de XMPP moderno"
|
||||||
|
|
||||||
#: main/data/im.dino.Dino.appdata.xml.in:9
|
#: main/data/im.dino.Dino.appdata.xml.in:9
|
||||||
|
@ -1037,7 +1037,7 @@ msgid "Check spelling"
|
|||||||
msgstr "Ortografia egiaztatu"
|
msgstr "Ortografia egiaztatu"
|
||||||
|
|
||||||
#: main/data/im.dino.Dino.appdata.xml.in:7
|
#: main/data/im.dino.Dino.appdata.xml.in:7
|
||||||
msgid "Modern XMPP Chat Client"
|
msgid "Modern XMPP chat client"
|
||||||
msgstr "XMPP txat bezero modernoa"
|
msgstr "XMPP txat bezero modernoa"
|
||||||
|
|
||||||
#: main/data/im.dino.Dino.appdata.xml.in:9
|
#: main/data/im.dino.Dino.appdata.xml.in:9
|
||||||
|
@ -1034,7 +1034,7 @@ msgid "Check spelling"
|
|||||||
msgstr "بررسی املا"
|
msgstr "بررسی املا"
|
||||||
|
|
||||||
#: main/data/im.dino.Dino.appdata.xml.in:7
|
#: main/data/im.dino.Dino.appdata.xml.in:7
|
||||||
msgid "Modern XMPP Chat Client"
|
msgid "Modern XMPP chat client"
|
||||||
msgstr "کلاینت نوین گپ XMPP"
|
msgstr "کلاینت نوین گپ XMPP"
|
||||||
|
|
||||||
#: main/data/im.dino.Dino.appdata.xml.in:9
|
#: main/data/im.dino.Dino.appdata.xml.in:9
|
||||||
|
@ -1036,7 +1036,7 @@ msgstr ""
|
|||||||
|
|
||||||
#: main/data/im.dino.Dino.appdata.xml.in:7
|
#: main/data/im.dino.Dino.appdata.xml.in:7
|
||||||
#, fuzzy
|
#, fuzzy
|
||||||
msgid "Modern XMPP Chat Client"
|
msgid "Modern XMPP chat client"
|
||||||
msgstr "Moderni XMPP-asiakasohjelma"
|
msgstr "Moderni XMPP-asiakasohjelma"
|
||||||
|
|
||||||
#: main/data/im.dino.Dino.appdata.xml.in:9
|
#: main/data/im.dino.Dino.appdata.xml.in:9
|
||||||
|
@ -1038,7 +1038,7 @@ msgid "Check spelling"
|
|||||||
msgstr "Vérifier l'orthographe"
|
msgstr "Vérifier l'orthographe"
|
||||||
|
|
||||||
#: main/data/im.dino.Dino.appdata.xml.in:7
|
#: main/data/im.dino.Dino.appdata.xml.in:7
|
||||||
msgid "Modern XMPP Chat Client"
|
msgid "Modern XMPP chat client"
|
||||||
msgstr "Client de clavardage XMPP moderne"
|
msgstr "Client de clavardage XMPP moderne"
|
||||||
|
|
||||||
#: main/data/im.dino.Dino.appdata.xml.in:9
|
#: main/data/im.dino.Dino.appdata.xml.in:9
|
||||||
|
@ -1038,7 +1038,7 @@ msgid "Check spelling"
|
|||||||
msgstr "Comprobar ortografía"
|
msgstr "Comprobar ortografía"
|
||||||
|
|
||||||
#: main/data/im.dino.Dino.appdata.xml.in:7
|
#: main/data/im.dino.Dino.appdata.xml.in:7
|
||||||
msgid "Modern XMPP Chat Client"
|
msgid "Modern XMPP chat client"
|
||||||
msgstr "Cliente moderno para conversas XMPP"
|
msgstr "Cliente moderno para conversas XMPP"
|
||||||
|
|
||||||
#: main/data/im.dino.Dino.appdata.xml.in:9
|
#: main/data/im.dino.Dino.appdata.xml.in:9
|
||||||
|
@ -1041,7 +1041,7 @@ msgid "Check spelling"
|
|||||||
msgstr "Helyesírás-ellenőrzés"
|
msgstr "Helyesírás-ellenőrzés"
|
||||||
|
|
||||||
#: main/data/im.dino.Dino.appdata.xml.in:7
|
#: main/data/im.dino.Dino.appdata.xml.in:7
|
||||||
msgid "Modern XMPP Chat Client"
|
msgid "Modern XMPP chat client"
|
||||||
msgstr "Modern XMPP csevegőprogram"
|
msgstr "Modern XMPP csevegőprogram"
|
||||||
|
|
||||||
#: main/data/im.dino.Dino.appdata.xml.in:9
|
#: main/data/im.dino.Dino.appdata.xml.in:9
|
||||||
|
@ -1030,7 +1030,7 @@ msgid "Check spelling"
|
|||||||
msgstr "Periksa ejaan"
|
msgstr "Periksa ejaan"
|
||||||
|
|
||||||
#: main/data/im.dino.Dino.appdata.xml.in:7
|
#: main/data/im.dino.Dino.appdata.xml.in:7
|
||||||
msgid "Modern XMPP Chat Client"
|
msgid "Modern XMPP chat client"
|
||||||
msgstr "Aplikasi chat XMPP modern"
|
msgstr "Aplikasi chat XMPP modern"
|
||||||
|
|
||||||
#: main/data/im.dino.Dino.appdata.xml.in:9
|
#: main/data/im.dino.Dino.appdata.xml.in:9
|
||||||
|
@ -1034,7 +1034,7 @@ msgid "Check spelling"
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: main/data/im.dino.Dino.appdata.xml.in:7
|
#: main/data/im.dino.Dino.appdata.xml.in:7
|
||||||
msgid "Modern XMPP Chat Client"
|
msgid "Modern XMPP chat client"
|
||||||
msgstr "Un modern client de conversationes XMPP"
|
msgstr "Un modern client de conversationes XMPP"
|
||||||
|
|
||||||
#: main/data/im.dino.Dino.appdata.xml.in:9
|
#: main/data/im.dino.Dino.appdata.xml.in:9
|
||||||
|
@ -1035,7 +1035,7 @@ msgid "Check spelling"
|
|||||||
msgstr "Kanna stafsetningu"
|
msgstr "Kanna stafsetningu"
|
||||||
|
|
||||||
#: main/data/im.dino.Dino.appdata.xml.in:7
|
#: main/data/im.dino.Dino.appdata.xml.in:7
|
||||||
msgid "Modern XMPP Chat Client"
|
msgid "Modern XMPP chat client"
|
||||||
msgstr "Nútímalegt XMPP-spjallforrit"
|
msgstr "Nútímalegt XMPP-spjallforrit"
|
||||||
|
|
||||||
#: main/data/im.dino.Dino.appdata.xml.in:9
|
#: main/data/im.dino.Dino.appdata.xml.in:9
|
||||||
|
@ -1037,7 +1037,7 @@ msgid "Check spelling"
|
|||||||
msgstr "Controlla l'ortografia"
|
msgstr "Controlla l'ortografia"
|
||||||
|
|
||||||
#: main/data/im.dino.Dino.appdata.xml.in:7
|
#: main/data/im.dino.Dino.appdata.xml.in:7
|
||||||
msgid "Modern XMPP Chat Client"
|
msgid "Modern XMPP chat client"
|
||||||
msgstr "Client di chat moderno per XMPP"
|
msgstr "Client di chat moderno per XMPP"
|
||||||
|
|
||||||
#: main/data/im.dino.Dino.appdata.xml.in:9
|
#: main/data/im.dino.Dino.appdata.xml.in:9
|
||||||
|
@ -1032,7 +1032,7 @@ msgid "Check spelling"
|
|||||||
msgstr "スペルチェック"
|
msgstr "スペルチェック"
|
||||||
|
|
||||||
#: main/data/im.dino.Dino.appdata.xml.in:7
|
#: main/data/im.dino.Dino.appdata.xml.in:7
|
||||||
msgid "Modern XMPP Chat Client"
|
msgid "Modern XMPP chat client"
|
||||||
msgstr "現代的な XMPP チャット クライアント"
|
msgstr "現代的な XMPP チャット クライアント"
|
||||||
|
|
||||||
#: main/data/im.dino.Dino.appdata.xml.in:9
|
#: main/data/im.dino.Dino.appdata.xml.in:9
|
||||||
|
@ -1034,7 +1034,7 @@ msgid "Check spelling"
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: main/data/im.dino.Dino.appdata.xml.in:7
|
#: main/data/im.dino.Dino.appdata.xml.in:7
|
||||||
msgid "Modern XMPP Chat Client"
|
msgid "Modern XMPP chat client"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: main/data/im.dino.Dino.appdata.xml.in:9
|
#: main/data/im.dino.Dino.appdata.xml.in:9
|
||||||
|
@ -1038,7 +1038,7 @@ msgid "Check spelling"
|
|||||||
msgstr "맞춤법 확인"
|
msgstr "맞춤법 확인"
|
||||||
|
|
||||||
#: main/data/im.dino.Dino.appdata.xml.in:7
|
#: main/data/im.dino.Dino.appdata.xml.in:7
|
||||||
msgid "Modern XMPP Chat Client"
|
msgid "Modern XMPP chat client"
|
||||||
msgstr "현대 XMPP 채팅 클라이언트"
|
msgstr "현대 XMPP 채팅 클라이언트"
|
||||||
|
|
||||||
#: main/data/im.dino.Dino.appdata.xml.in:9
|
#: main/data/im.dino.Dino.appdata.xml.in:9
|
||||||
|
@ -1030,7 +1030,7 @@ msgid "Check spelling"
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: main/data/im.dino.Dino.appdata.xml.in:7
|
#: main/data/im.dino.Dino.appdata.xml.in:7
|
||||||
msgid "Modern XMPP Chat Client"
|
msgid "Modern XMPP chat client"
|
||||||
msgstr "Modernen XMPP Chat Client"
|
msgstr "Modernen XMPP Chat Client"
|
||||||
|
|
||||||
#: main/data/im.dino.Dino.appdata.xml.in:9
|
#: main/data/im.dino.Dino.appdata.xml.in:9
|
||||||
|
@ -1041,7 +1041,7 @@ msgid "Check spelling"
|
|||||||
msgstr "Tikrinti rašybą"
|
msgstr "Tikrinti rašybą"
|
||||||
|
|
||||||
#: main/data/im.dino.Dino.appdata.xml.in:7
|
#: main/data/im.dino.Dino.appdata.xml.in:7
|
||||||
msgid "Modern XMPP Chat Client"
|
msgid "Modern XMPP chat client"
|
||||||
msgstr "Šiuolaikinė XMPP pokalbių kliento programa"
|
msgstr "Šiuolaikinė XMPP pokalbių kliento programa"
|
||||||
|
|
||||||
#: main/data/im.dino.Dino.appdata.xml.in:9
|
#: main/data/im.dino.Dino.appdata.xml.in:9
|
||||||
|
@ -1035,7 +1035,7 @@ msgid "Check spelling"
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: main/data/im.dino.Dino.appdata.xml.in:7
|
#: main/data/im.dino.Dino.appdata.xml.in:7
|
||||||
msgid "Modern XMPP Chat Client"
|
msgid "Modern XMPP chat client"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: main/data/im.dino.Dino.appdata.xml.in:9
|
#: main/data/im.dino.Dino.appdata.xml.in:9
|
||||||
|
@ -1039,7 +1039,7 @@ msgid "Check spelling"
|
|||||||
msgstr "Stavekontroll"
|
msgstr "Stavekontroll"
|
||||||
|
|
||||||
#: main/data/im.dino.Dino.appdata.xml.in:7
|
#: main/data/im.dino.Dino.appdata.xml.in:7
|
||||||
msgid "Modern XMPP Chat Client"
|
msgid "Modern XMPP chat client"
|
||||||
msgstr "Moderne XMPP-sludreklient"
|
msgstr "Moderne XMPP-sludreklient"
|
||||||
|
|
||||||
#: main/data/im.dino.Dino.appdata.xml.in:9
|
#: main/data/im.dino.Dino.appdata.xml.in:9
|
||||||
|
@ -1035,7 +1035,7 @@ msgid "Check spelling"
|
|||||||
msgstr "Spelling controleren"
|
msgstr "Spelling controleren"
|
||||||
|
|
||||||
#: main/data/im.dino.Dino.appdata.xml.in:7
|
#: main/data/im.dino.Dino.appdata.xml.in:7
|
||||||
msgid "Modern XMPP Chat Client"
|
msgid "Modern XMPP chat client"
|
||||||
msgstr "Een moderne XMPP-chatclient"
|
msgstr "Een moderne XMPP-chatclient"
|
||||||
|
|
||||||
#: main/data/im.dino.Dino.appdata.xml.in:9
|
#: main/data/im.dino.Dino.appdata.xml.in:9
|
||||||
|
@ -1037,7 +1037,7 @@ msgid "Check spelling"
|
|||||||
msgstr "Verificar l’ortografia"
|
msgstr "Verificar l’ortografia"
|
||||||
|
|
||||||
#: main/data/im.dino.Dino.appdata.xml.in:7
|
#: main/data/im.dino.Dino.appdata.xml.in:7
|
||||||
msgid "Modern XMPP Chat Client"
|
msgid "Modern XMPP chat client"
|
||||||
msgstr "Client XMPP modèrn"
|
msgstr "Client XMPP modèrn"
|
||||||
|
|
||||||
#: main/data/im.dino.Dino.appdata.xml.in:9
|
#: main/data/im.dino.Dino.appdata.xml.in:9
|
||||||
|
@ -1042,7 +1042,7 @@ msgid "Check spelling"
|
|||||||
msgstr "Sprawdź pisownie"
|
msgstr "Sprawdź pisownie"
|
||||||
|
|
||||||
#: main/data/im.dino.Dino.appdata.xml.in:7
|
#: main/data/im.dino.Dino.appdata.xml.in:7
|
||||||
msgid "Modern XMPP Chat Client"
|
msgid "Modern XMPP chat client"
|
||||||
msgstr "Nowoczesny komunikator XMPP"
|
msgstr "Nowoczesny komunikator XMPP"
|
||||||
|
|
||||||
#: main/data/im.dino.Dino.appdata.xml.in:9
|
#: main/data/im.dino.Dino.appdata.xml.in:9
|
||||||
|
@ -1035,7 +1035,7 @@ msgid "Check spelling"
|
|||||||
msgstr "Verificar ortografia"
|
msgstr "Verificar ortografia"
|
||||||
|
|
||||||
#: main/data/im.dino.Dino.appdata.xml.in:7
|
#: main/data/im.dino.Dino.appdata.xml.in:7
|
||||||
msgid "Modern XMPP Chat Client"
|
msgid "Modern XMPP chat client"
|
||||||
msgstr "Moderno cliente de chat XMPP"
|
msgstr "Moderno cliente de chat XMPP"
|
||||||
|
|
||||||
#: main/data/im.dino.Dino.appdata.xml.in:9
|
#: main/data/im.dino.Dino.appdata.xml.in:9
|
||||||
|
@ -1036,7 +1036,7 @@ msgid "Check spelling"
|
|||||||
msgstr "Verificar ortografia"
|
msgstr "Verificar ortografia"
|
||||||
|
|
||||||
#: main/data/im.dino.Dino.appdata.xml.in:7
|
#: main/data/im.dino.Dino.appdata.xml.in:7
|
||||||
msgid "Modern XMPP Chat Client"
|
msgid "Modern XMPP chat client"
|
||||||
msgstr "Cliente de Chat XMPP Moderno"
|
msgstr "Cliente de Chat XMPP Moderno"
|
||||||
|
|
||||||
#: main/data/im.dino.Dino.appdata.xml.in:9
|
#: main/data/im.dino.Dino.appdata.xml.in:9
|
||||||
|
@ -1042,7 +1042,7 @@ msgid "Check spelling"
|
|||||||
msgstr "Verificare ortografie"
|
msgstr "Verificare ortografie"
|
||||||
|
|
||||||
#: main/data/im.dino.Dino.appdata.xml.in:7
|
#: main/data/im.dino.Dino.appdata.xml.in:7
|
||||||
msgid "Modern XMPP Chat Client"
|
msgid "Modern XMPP chat client"
|
||||||
msgstr "Client XMPP de discuții modern"
|
msgstr "Client XMPP de discuții modern"
|
||||||
|
|
||||||
#: main/data/im.dino.Dino.appdata.xml.in:9
|
#: main/data/im.dino.Dino.appdata.xml.in:9
|
||||||
|
@ -1040,7 +1040,7 @@ msgid "Check spelling"
|
|||||||
msgstr "Проверка орфографии"
|
msgstr "Проверка орфографии"
|
||||||
|
|
||||||
#: main/data/im.dino.Dino.appdata.xml.in:7
|
#: main/data/im.dino.Dino.appdata.xml.in:7
|
||||||
msgid "Modern XMPP Chat Client"
|
msgid "Modern XMPP chat client"
|
||||||
msgstr "Современный XMPP клиент"
|
msgstr "Современный XMPP клиент"
|
||||||
|
|
||||||
#: main/data/im.dino.Dino.appdata.xml.in:9
|
#: main/data/im.dino.Dino.appdata.xml.in:9
|
||||||
|
@ -1035,7 +1035,7 @@ msgid "Check spelling"
|
|||||||
msgstr "Kontroll drejtshkrimi"
|
msgstr "Kontroll drejtshkrimi"
|
||||||
|
|
||||||
#: main/data/im.dino.Dino.appdata.xml.in:7
|
#: main/data/im.dino.Dino.appdata.xml.in:7
|
||||||
msgid "Modern XMPP Chat Client"
|
msgid "Modern XMPP chat client"
|
||||||
msgstr "Klient Modern Fjalosjesh XMPP"
|
msgstr "Klient Modern Fjalosjesh XMPP"
|
||||||
|
|
||||||
#: main/data/im.dino.Dino.appdata.xml.in:9
|
#: main/data/im.dino.Dino.appdata.xml.in:9
|
||||||
|
@ -1035,7 +1035,7 @@ msgid "Check spelling"
|
|||||||
msgstr "Kontrollera stavning"
|
msgstr "Kontrollera stavning"
|
||||||
|
|
||||||
#: main/data/im.dino.Dino.appdata.xml.in:7
|
#: main/data/im.dino.Dino.appdata.xml.in:7
|
||||||
msgid "Modern XMPP Chat Client"
|
msgid "Modern XMPP chat client"
|
||||||
msgstr "Modern XMPP-chattklient"
|
msgstr "Modern XMPP-chattklient"
|
||||||
|
|
||||||
#: main/data/im.dino.Dino.appdata.xml.in:9
|
#: main/data/im.dino.Dino.appdata.xml.in:9
|
||||||
|
@ -1034,7 +1034,7 @@ msgid "Check spelling"
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: main/data/im.dino.Dino.appdata.xml.in:7
|
#: main/data/im.dino.Dino.appdata.xml.in:7
|
||||||
msgid "Modern XMPP Chat Client"
|
msgid "Modern XMPP chat client"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: main/data/im.dino.Dino.appdata.xml.in:9
|
#: main/data/im.dino.Dino.appdata.xml.in:9
|
||||||
|
@ -1034,7 +1034,7 @@ msgid "Check spelling"
|
|||||||
msgstr "Yazım denetimi"
|
msgstr "Yazım denetimi"
|
||||||
|
|
||||||
#: main/data/im.dino.Dino.appdata.xml.in:7
|
#: main/data/im.dino.Dino.appdata.xml.in:7
|
||||||
msgid "Modern XMPP Chat Client"
|
msgid "Modern XMPP chat client"
|
||||||
msgstr "Modern XMPP Sohbet İstemcisi"
|
msgstr "Modern XMPP Sohbet İstemcisi"
|
||||||
|
|
||||||
#: main/data/im.dino.Dino.appdata.xml.in:9
|
#: main/data/im.dino.Dino.appdata.xml.in:9
|
||||||
|
@ -1039,7 +1039,7 @@ msgid "Check spelling"
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: main/data/im.dino.Dino.appdata.xml.in:7
|
#: main/data/im.dino.Dino.appdata.xml.in:7
|
||||||
msgid "Modern XMPP Chat Client"
|
msgid "Modern XMPP chat client"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: main/data/im.dino.Dino.appdata.xml.in:9
|
#: main/data/im.dino.Dino.appdata.xml.in:9
|
||||||
|
@ -1031,7 +1031,7 @@ msgid "Check spelling"
|
|||||||
msgstr "检查拼写"
|
msgstr "检查拼写"
|
||||||
|
|
||||||
#: main/data/im.dino.Dino.appdata.xml.in:7
|
#: main/data/im.dino.Dino.appdata.xml.in:7
|
||||||
msgid "Modern XMPP Chat Client"
|
msgid "Modern XMPP chat client"
|
||||||
msgstr "现代 XMPP 聊天客户端"
|
msgstr "现代 XMPP 聊天客户端"
|
||||||
|
|
||||||
#: main/data/im.dino.Dino.appdata.xml.in:9
|
#: main/data/im.dino.Dino.appdata.xml.in:9
|
||||||
|
@ -1030,7 +1030,7 @@ msgid "Check spelling"
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: main/data/im.dino.Dino.appdata.xml.in:7
|
#: main/data/im.dino.Dino.appdata.xml.in:7
|
||||||
msgid "Modern XMPP Chat Client"
|
msgid "Modern XMPP chat client"
|
||||||
msgstr "現代化的 XMPP 用戶端聊天軟件"
|
msgstr "現代化的 XMPP 用戶端聊天軟件"
|
||||||
|
|
||||||
#: main/data/im.dino.Dino.appdata.xml.in:9
|
#: main/data/im.dino.Dino.appdata.xml.in:9
|
||||||
|
@ -101,6 +101,7 @@ public class AddConferenceDialog : Gtk.Dialog {
|
|||||||
});
|
});
|
||||||
select_fragment.remove_jid.connect((row) => {
|
select_fragment.remove_jid.connect((row) => {
|
||||||
ConferenceListRow conference_row = row as ConferenceListRow;
|
ConferenceListRow conference_row = row as ConferenceListRow;
|
||||||
|
if (conference_row == null) return;
|
||||||
stream_interactor.get_module(MucManager.IDENTITY).remove_bookmark(conference_row.account, conference_row.bookmark);
|
stream_interactor.get_module(MucManager.IDENTITY).remove_bookmark(conference_row.account, conference_row.bookmark);
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -162,7 +163,7 @@ public class AddConferenceDialog : Gtk.Dialog {
|
|||||||
details_fragment.clear();
|
details_fragment.clear();
|
||||||
|
|
||||||
ListRow? row = conference_list_box.get_selected_row() != null ? conference_list_box.get_selected_row().get_child() as ListRow : null;
|
ListRow? row = conference_list_box.get_selected_row() != null ? conference_list_box.get_selected_row().get_child() as ListRow : null;
|
||||||
ConferenceListRow? conference_row = conference_list_box.get_selected_row() != null ? conference_list_box.get_selected_row() as ConferenceListRow : null;
|
ConferenceListRow? conference_row = conference_list_box.get_selected_row() != null ? conference_list_box.get_selected_row().get_child() as ConferenceListRow : null;
|
||||||
if (conference_row != null) {
|
if (conference_row != null) {
|
||||||
details_fragment.account = conference_row.account;
|
details_fragment.account = conference_row.account;
|
||||||
details_fragment.jid = conference_row.bookmark.jid.to_string();
|
details_fragment.jid = conference_row.bookmark.jid.to_string();
|
||||||
|
@ -138,7 +138,7 @@ public class Dino.Ui.CallBottomBar : Gtk.Box {
|
|||||||
|
|
||||||
public void show_counterpart_ended(string text) {
|
public void show_counterpart_ended(string text) {
|
||||||
stack.set_visible_child_name("label");
|
stack.set_visible_child_name("label");
|
||||||
label.label = text;
|
label.label = Util.unbreak_space_around_non_spacing_mark(text);
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool is_menu_active() {
|
public bool is_menu_active() {
|
||||||
|
@ -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,10 +94,16 @@ 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);
|
// Let the default handler normally insert a newline if shift was hold
|
||||||
|
return false;
|
||||||
} else if (text_view.buffer.text.strip() != "") {
|
} else if (text_view.buffer.text.strip() != "") {
|
||||||
send_text();
|
send_text();
|
||||||
}
|
}
|
||||||
|
@ -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"));
|
||||||
|
@ -18,7 +18,7 @@ public class ConversationView : Widget, Plugins.ConversationItemCollection, Plug
|
|||||||
[GtkChild] private unowned Box message_menu_box;
|
[GtkChild] private unowned Box message_menu_box;
|
||||||
[GtkChild] private unowned Box notifications;
|
[GtkChild] private unowned Box notifications;
|
||||||
[GtkChild] private unowned Box main;
|
[GtkChild] private unowned Box main;
|
||||||
[GtkChild] private unowned Box main_wrap_box;
|
[GtkChild] private unowned Widget main_wrap_box;
|
||||||
|
|
||||||
private HashMap<string, Widget> action_buttons = new HashMap<string, Widget>();
|
private HashMap<string, Widget> action_buttons = new HashMap<string, Widget>();
|
||||||
private Gee.List<Dino.Plugins.MessageAction>? message_actions = null;
|
private Gee.List<Dino.Plugins.MessageAction>? message_actions = null;
|
||||||
@ -46,6 +46,7 @@ public class ConversationView : Widget, Plugins.ConversationItemCollection, Plug
|
|||||||
|
|
||||||
construct {
|
construct {
|
||||||
this.layout_manager = new BinLayout();
|
this.layout_manager = new BinLayout();
|
||||||
|
main_wrap_box.layout_manager = new BinLayout();
|
||||||
|
|
||||||
// Setup all message menu buttons
|
// Setup all message menu buttons
|
||||||
var correction_button = new Button() { name="correction" };
|
var correction_button = new Button() { name="correction" };
|
||||||
@ -95,7 +96,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.
|
||||||
@ -607,6 +607,12 @@ public class ConversationView : Widget, Plugins.ConversationItemCollection, Plug
|
|||||||
widget.dispose();
|
widget.dispose();
|
||||||
}
|
}
|
||||||
widgets.clear();
|
widgets.clear();
|
||||||
|
|
||||||
|
Widget? notification = notifications.get_first_child();
|
||||||
|
while (notification != null) {
|
||||||
|
notifications.remove(notification);
|
||||||
|
notification = notifications.get_first_child();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void clear_notifications() {
|
private void clear_notifications() {
|
||||||
|
@ -6,7 +6,11 @@ using Dino.Entities;
|
|||||||
|
|
||||||
namespace Dino.Ui {
|
namespace Dino.Ui {
|
||||||
|
|
||||||
public class FileImageWidget : Box {
|
public class FileImageWidget : Widget {
|
||||||
|
|
||||||
|
construct {
|
||||||
|
layout_manager = new BinLayout();
|
||||||
|
}
|
||||||
|
|
||||||
public FileImageWidget() {
|
public FileImageWidget() {
|
||||||
this.halign = Align.START;
|
this.halign = Align.START;
|
||||||
@ -74,7 +78,12 @@ public class FileImageWidget : Box {
|
|||||||
image_overlay_toolbar.visible = false;
|
image_overlay_toolbar.visible = false;
|
||||||
});
|
});
|
||||||
|
|
||||||
this.append(overlay);
|
overlay.insert_after(this, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void dispose() {
|
||||||
|
if (get_first_child() != null && get_first_child().parent != null) get_first_child().unparent();
|
||||||
|
base.dispose();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -39,7 +39,7 @@ public class FileMetaItem : ConversationSummary.ContentMetaItem {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public class FileWidget : SizeRequestBox {
|
public class FileWidget : SizeRequestBin {
|
||||||
|
|
||||||
enum State {
|
enum State {
|
||||||
IMAGE,
|
IMAGE,
|
||||||
@ -100,22 +100,22 @@ public class FileWidget : SizeRequestBox {
|
|||||||
// If the widget changed in the meanwhile, stop
|
// If the widget changed in the meanwhile, stop
|
||||||
if (content != content_bak) return;
|
if (content != content_bak) return;
|
||||||
|
|
||||||
if (content != null) this.remove(content);
|
if (content != null) content.unparent();
|
||||||
content = file_image_widget;
|
content = file_image_widget;
|
||||||
state = State.IMAGE;
|
state = State.IMAGE;
|
||||||
this.append(content);
|
content.insert_after(this, null);
|
||||||
return;
|
return;
|
||||||
} catch (Error e) { }
|
} catch (Error e) { }
|
||||||
}
|
}
|
||||||
|
|
||||||
if (state != State.DEFAULT) {
|
if (state != State.DEFAULT) {
|
||||||
if (content != null) this.remove(content);
|
if (content != null) content.unparent();
|
||||||
FileDefaultWidget default_file_widget = new FileDefaultWidget();
|
FileDefaultWidget default_file_widget = new FileDefaultWidget();
|
||||||
default_widget_controller = new FileDefaultWidgetController(default_file_widget);
|
default_widget_controller = new FileDefaultWidgetController(default_file_widget);
|
||||||
default_widget_controller.set_file_transfer(file_transfer);
|
default_widget_controller.set_file_transfer(file_transfer);
|
||||||
content = default_file_widget;
|
content = default_file_widget;
|
||||||
this.state = State.DEFAULT;
|
this.state = State.DEFAULT;
|
||||||
this.append(content);
|
content.insert_after(this, null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -87,6 +87,9 @@ public class MessageMetaItem : ContentMetaItem {
|
|||||||
markup_text = markup_text.substring(4);
|
markup_text = markup_text.substring(4);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Work around pango bug
|
||||||
|
markup_text = Util.unbreak_space_around_non_spacing_mark((owned) markup_text);
|
||||||
|
|
||||||
if (conversation.type_ == Conversation.Type.GROUPCHAT) {
|
if (conversation.type_ == Conversation.Type.GROUPCHAT) {
|
||||||
markup_text = Util.parse_add_markup_theme(markup_text, conversation.nickname, true, true, true, Util.is_dark_theme(this.label), ref theme_dependent);
|
markup_text = Util.parse_add_markup_theme(markup_text, conversation.nickname, true, true, true, Util.is_dark_theme(this.label), ref theme_dependent);
|
||||||
} else {
|
} else {
|
||||||
|
@ -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;
|
||||||
|
@ -223,15 +223,16 @@ public class GlobalSearch {
|
|||||||
grid.margin_top = 3;
|
grid.margin_top = 3;
|
||||||
grid.margin_bottom = 3;
|
grid.margin_bottom = 3;
|
||||||
|
|
||||||
string text = item.message.body.replace("\n", "").replace("\r", "");
|
string text = Util.unbreak_space_around_non_spacing_mark(item.message.body.replace("\n", "").replace("\r", ""));
|
||||||
if (text.length > 200) {
|
if (text.char_count() > 200) {
|
||||||
int index = text.index_of(search);
|
int index = text.index_of(search);
|
||||||
if (index + search.length <= 100) {
|
int char_index = index < 0 ? 0 : text.char_count(index);
|
||||||
text = text.substring(0, 150) + " … " + text.substring(text.length - 50, 50);
|
if (char_index + search.char_count() <= 100) {
|
||||||
} else if (index >= text.length - 100) {
|
text = text.substring(0, text.index_of_nth_char(150)) + " … " + text.substring(text.index_of_nth_char(text.char_count() - 50));
|
||||||
text = text.substring(0, 50) + " … " + text.substring(text.length - 150, 150);
|
} else if (char_index >= text.char_count() - 100) {
|
||||||
|
text = text.substring(0, text.index_of_nth_char(50)) + " … " + text.substring(text.index_of_nth_char(text.char_count() - 150));
|
||||||
} else {
|
} else {
|
||||||
text = text.substring(0, 25) + " … " + text.substring(index - 50, 50) + text.substring(index, 100) + " … " + text.substring(text.length - 25, 25);
|
text = text.substring(0, text.index_of_nth_char(25)) + " … " + text.substring(text.index_of_nth_char(char_index - 50), text.index_of_nth_char(char_index + 100)) + " … " + text.substring(text.index_of_nth_char(text.char_count() - 25));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Label label = new Label("") { use_markup=true, xalign=0, selectable=true, wrap=true, wrap_mode=Pango.WrapMode.WORD_CHAR, vexpand=true };
|
Label label = new Label("") { use_markup=true, xalign=0, selectable=true, wrap=true, wrap_mode=Pango.WrapMode.WORD_CHAR, vexpand=true };
|
||||||
@ -260,7 +261,8 @@ public class GlobalSearch {
|
|||||||
for (; match_info.matches(); match_info.next()) {
|
for (; match_info.matches(); match_info.next()) {
|
||||||
int start, end;
|
int start, end;
|
||||||
match_info.fetch_pos(0, out start, out end);
|
match_info.fetch_pos(0, out start, out end);
|
||||||
markup_text += Markup.escape_text(text[last_end:start]) + "<span bgcolor=\"yellow\">" + Markup.escape_text(text[start:end]) + "</span>";
|
string themed_span = Util.is_dark_theme(label) ? "<span color=\"black\" bgcolor=\"yellow\">" : "<span bgcolor=\"yellow\">";
|
||||||
|
markup_text += Markup.escape_text(text[last_end:start]) + themed_span + Markup.escape_text(text[start:end]) + "</span>";
|
||||||
last_end = end;
|
last_end = end;
|
||||||
}
|
}
|
||||||
markup_text += Markup.escape_text(text[last_end:text.length]);
|
markup_text += Markup.escape_text(text[last_end:text.length]);
|
||||||
|
@ -345,7 +345,7 @@ public class AddAccountDialog : Gtk.Dialog {
|
|||||||
register_form_continue.grab_focus();
|
register_form_continue.grab_focus();
|
||||||
} else if (form.fields.size > 0) {
|
} else if (form.fields.size > 0) {
|
||||||
if (form.instructions != null && form.instructions != "") {
|
if (form.instructions != null && form.instructions != "") {
|
||||||
string markup_instructions = Util.parse_add_markup(form.instructions, null, true, false);
|
string markup_instructions = Util.parse_add_markup(Util.unbreak_space_around_non_spacing_mark(form.instructions), null, true, false);
|
||||||
form_box.append(new Label(markup_instructions) { use_markup=true, halign=Align.CENTER, xalign=0, margin_top=7,
|
form_box.append(new Label(markup_instructions) { use_markup=true, halign=Align.CENTER, xalign=0, margin_top=7,
|
||||||
wrap=true, wrap_mode=Pango.WrapMode.WORD_CHAR });
|
wrap=true, wrap_mode=Pango.WrapMode.WORD_CHAR });
|
||||||
}
|
}
|
||||||
|
@ -267,7 +267,6 @@ public class Dino.Ui.FreeDesktopNotifier : NotificationProvider, Object {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public async void retract_content_item_notifications() {
|
public async void retract_content_item_notifications() {
|
||||||
if (content_notifications != null) {
|
|
||||||
foreach (uint32 id in content_notifications.values) {
|
foreach (uint32 id in content_notifications.values) {
|
||||||
try {
|
try {
|
||||||
dbus_notifications.close_notification.begin(id);
|
dbus_notifications.close_notification.begin(id);
|
||||||
@ -275,17 +274,23 @@ public class Dino.Ui.FreeDesktopNotifier : NotificationProvider, Object {
|
|||||||
}
|
}
|
||||||
content_notifications.clear();
|
content_notifications.clear();
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
public async void retract_conversation_notifications(Conversation conversation) {
|
public async void retract_conversation_notifications(Conversation conversation) {
|
||||||
if (content_notifications.has_key(conversation)) {
|
|
||||||
try {
|
try {
|
||||||
|
if (content_notifications.has_key(conversation)) {
|
||||||
dbus_notifications.close_notification.begin(content_notifications[conversation]);
|
dbus_notifications.close_notification.begin(content_notifications[conversation]);
|
||||||
} catch (Error e) { }
|
|
||||||
}
|
|
||||||
content_notifications.unset(conversation);
|
content_notifications.unset(conversation);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (conversation_notifications.has_key(conversation)) {
|
||||||
|
foreach (var notification_id in conversation_notifications[conversation]) {
|
||||||
|
dbus_notifications.close_notification.begin(notification_id);
|
||||||
|
}
|
||||||
|
conversation_notifications.unset(conversation);
|
||||||
|
}
|
||||||
|
} catch (Error e) { }
|
||||||
|
}
|
||||||
|
|
||||||
private async Variant get_conversation_icon(Conversation conversation) {
|
private async Variant get_conversation_icon(Conversation conversation) {
|
||||||
AvatarDrawer drawer = yield Util.get_conversation_avatar_drawer(stream_interactor, conversation);
|
AvatarDrawer drawer = yield Util.get_conversation_avatar_drawer(stream_interactor, conversation);
|
||||||
Cairo.ImageSurface surface = drawer.size(40, 40).draw_image_surface();
|
Cairo.ImageSurface surface = drawer.size(40, 40).draw_image_surface();
|
||||||
|
@ -238,6 +238,41 @@ public static Map<unichar, unichar> get_matching_chars() {
|
|||||||
return MATCHING_CHARS;
|
return MATCHING_CHARS;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This replaces spaces with non-breaking spaces when they are adjacent to a non-spacing mark.
|
||||||
|
*
|
||||||
|
* We do this to work-around a bug in Pango. See https://gitlab.gnome.org/GNOME/pango/-/issues/798 and
|
||||||
|
* https://gitlab.gnome.org/GNOME/pango/-/issues/832
|
||||||
|
*
|
||||||
|
* This is zero-copy iff no space is adjacent to a non-spacing mark, otherwise the provided string will be destroyed
|
||||||
|
* and the returned string should be used instead.
|
||||||
|
*/
|
||||||
|
public static string unbreak_space_around_non_spacing_mark(owned string s) {
|
||||||
|
int current_index = 0;
|
||||||
|
unichar current_char = 0;
|
||||||
|
int prev_index = 0;
|
||||||
|
unichar prev_char = 0;
|
||||||
|
bool is_non_spacing_mark = false;
|
||||||
|
while (s.get_next_char(ref current_index, out current_char)) {
|
||||||
|
int replace_index = -1;
|
||||||
|
if (is_non_spacing_mark && current_char == ' ') {
|
||||||
|
replace_index = prev_index;
|
||||||
|
current_char = ' ';
|
||||||
|
}
|
||||||
|
is_non_spacing_mark = ICU.get_int_property_value(current_char, ICU.Property.BIDI_CLASS) == ICU.CharDirection.DIR_NON_SPACING_MARK;
|
||||||
|
if (prev_char == ' ' && is_non_spacing_mark) {
|
||||||
|
replace_index = prev_index - 1;
|
||||||
|
}
|
||||||
|
if (replace_index != -1) {
|
||||||
|
s = s[0:replace_index] + " " + s[(replace_index + 1):s.length];
|
||||||
|
current_index += 1;
|
||||||
|
}
|
||||||
|
prev_index = current_index;
|
||||||
|
prev_char = current_char;
|
||||||
|
}
|
||||||
|
return (owned) s;
|
||||||
|
}
|
||||||
|
|
||||||
public static string parse_add_markup(string s_, string? highlight_word, bool parse_links, bool parse_text_markup) {
|
public static string parse_add_markup(string s_, string? highlight_word, bool parse_links, bool parse_text_markup) {
|
||||||
bool ignore_out_var = false;
|
bool ignore_out_var = false;
|
||||||
return parse_add_markup_theme(s_, highlight_word, parse_links, parse_text_markup, parse_text_markup, false, ref ignore_out_var);
|
return parse_add_markup_theme(s_, highlight_word, parse_links, parse_text_markup, parse_text_markup, false, ref ignore_out_var);
|
||||||
|
@ -16,6 +16,17 @@ public class SizeRequestBin : Widget {
|
|||||||
this.layout_manager = new BinLayout();
|
this.layout_manager = new BinLayout();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public override void compute_expand_internal(out bool hexpand, out bool vexpand) {
|
||||||
|
hexpand = false;
|
||||||
|
vexpand = false;
|
||||||
|
Widget child = get_first_child();
|
||||||
|
while (child != null) {
|
||||||
|
hexpand = hexpand || child.compute_expand(Orientation.HORIZONTAL);
|
||||||
|
vexpand = vexpand || child.compute_expand(Orientation.VERTICAL);
|
||||||
|
child = child.get_next_sibling();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public override Gtk.SizeRequestMode get_request_mode() {
|
public override Gtk.SizeRequestMode get_request_mode() {
|
||||||
return size_request_mode;
|
return size_request_mode;
|
||||||
}
|
}
|
||||||
|
@ -9,29 +9,72 @@ public class SizingBin : Widget {
|
|||||||
public int target_height { get; set; default = -1; }
|
public int target_height { get; set; default = -1; }
|
||||||
public int max_height { get; set; default = -1; }
|
public int max_height { get; set; default = -1; }
|
||||||
|
|
||||||
construct {
|
public override void compute_expand_internal(out bool hexpand, out bool vexpand) {
|
||||||
layout_manager = new BinLayout();
|
hexpand = false;
|
||||||
|
vexpand = false;
|
||||||
|
Widget child = get_first_child();
|
||||||
|
while (child != null) {
|
||||||
|
hexpand = hexpand || child.compute_expand(Orientation.HORIZONTAL);
|
||||||
|
vexpand = vexpand || child.compute_expand(Orientation.VERTICAL);
|
||||||
|
child = child.get_next_sibling();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void size_allocate(int width, int height, int baseline) {
|
public override void size_allocate(int width, int height, int baseline) {
|
||||||
if (max_height != -1) height = int.min(height, max_height);
|
if (max_height != -1) height = int.min(height, max_height);
|
||||||
if (max_width != -1) width = int.min(width, max_width);
|
if (max_width != -1) width = int.min(width, max_width);
|
||||||
base.size_allocate(width, height, baseline);
|
Widget child = get_first_child();
|
||||||
|
while (child != null) {
|
||||||
|
if (child.should_layout()) {
|
||||||
|
child.allocate(width, height, baseline, null);
|
||||||
|
}
|
||||||
|
child = child.get_next_sibling();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void measure(Orientation orientation, int for_size, out int minimum, out int natural, out int minimum_baseline, out int natural_baseline) {
|
public override void measure(Orientation orientation, int for_size, out int minimum, out int natural, out int minimum_baseline, out int natural_baseline) {
|
||||||
base.measure(orientation, for_size, out minimum, out natural, out minimum_baseline, out natural_baseline);
|
|
||||||
if (orientation == Orientation.HORIZONTAL) {
|
if (orientation == Orientation.HORIZONTAL) {
|
||||||
if (min_width != -1) minimum = int.max(minimum, min_width);
|
minimum = min_width;
|
||||||
if (max_width != -1) natural = int.min(natural, max_width);
|
natural = target_width;
|
||||||
if (target_width != -1) natural = int.max(natural, target_width);
|
} else {
|
||||||
natural = int.max(natural, minimum);
|
minimum = min_height;
|
||||||
|
natural = target_height;
|
||||||
|
}
|
||||||
|
minimum_baseline = -1;
|
||||||
|
natural_baseline = -1;
|
||||||
|
Widget child = get_first_child();
|
||||||
|
while (child != null) {
|
||||||
|
if (child.should_layout()) {
|
||||||
|
int child_min = 0;
|
||||||
|
int child_nat = 0;
|
||||||
|
int child_min_baseline = -1;
|
||||||
|
int child_nat_baseline = -1;
|
||||||
|
child.measure(orientation, for_size, out child_min, out child_nat, out child_min_baseline, out child_nat_baseline);
|
||||||
|
minimum = int.max(minimum, child_min);
|
||||||
|
natural = int.max(natural, child_nat);
|
||||||
|
if (child_min_baseline > 0) {
|
||||||
|
minimum_baseline = int.max(minimum_baseline, child_min_baseline);
|
||||||
|
}
|
||||||
|
if (child_nat_baseline > 0) {
|
||||||
|
natural_baseline = int.max(natural_baseline, child_nat_baseline);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
child = child.get_next_sibling();
|
||||||
|
}
|
||||||
|
if (orientation == Orientation.HORIZONTAL) {
|
||||||
|
if (max_width != -1) natural = int.min(natural, max_width);
|
||||||
} else {
|
} else {
|
||||||
if (min_height != -1) minimum = int.max(minimum, min_height);
|
|
||||||
if (max_height != -1) natural = int.min(natural, max_height);
|
if (max_height != -1) natural = int.min(natural, max_height);
|
||||||
if (target_height != -1) natural = int.max(natural, target_height);
|
}
|
||||||
natural = int.max(natural, minimum);
|
natural = int.max(natural, minimum);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public override SizeRequestMode get_request_mode() {
|
||||||
|
Widget child = get_first_child();
|
||||||
|
if (child != null) {
|
||||||
|
return child.get_request_mode();
|
||||||
|
}
|
||||||
|
return SizeRequestMode.CONSTANT_SIZE;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void dispose() {
|
public override void dispose() {
|
||||||
|
@ -62,12 +62,12 @@ class Dino.Ui.FixedRatioPicture : Gtk.Widget {
|
|||||||
measure_target_size(out width, out height);
|
measure_target_size(out width, out height);
|
||||||
if (orientation == Orientation.HORIZONTAL) {
|
if (orientation == Orientation.HORIZONTAL) {
|
||||||
minimum = min_width;
|
minimum = min_width;
|
||||||
natural = width;
|
natural = int.max(min_width, int.min(width, max_width));
|
||||||
} else if (for_size == -1) {
|
} else if (for_size == -1) {
|
||||||
minimum = min_height;
|
minimum = min_height;
|
||||||
natural = height;
|
natural = int.max(min_height, int.min(height, max_height));
|
||||||
} else {
|
} else {
|
||||||
minimum = natural = height * for_size / width;
|
minimum = natural = int.max(min_height, int.min(height * for_size / width, height));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,21 +1,31 @@
|
|||||||
using Gtk;
|
using Gtk;
|
||||||
|
|
||||||
public class Dino.Ui.NaturalSizeIncrease : Gtk.Widget {
|
public class Dino.Ui.NaturalSizeIncrease : Gtk.Widget {
|
||||||
public int min_natural_height { get; set; default = -1; }
|
public uint min_natural_height { get; set; default = 0; }
|
||||||
public int min_natural_width { get; set; default = -1; }
|
public uint min_natural_width { get; set; default = 0; }
|
||||||
|
|
||||||
construct {
|
construct {
|
||||||
this.notify.connect(queue_resize);
|
this.notify.connect(queue_resize);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public override void compute_expand_internal(out bool hexpand, out bool vexpand) {
|
||||||
|
hexpand = false;
|
||||||
|
vexpand = false;
|
||||||
|
Widget child = get_first_child();
|
||||||
|
while (child != null) {
|
||||||
|
hexpand = hexpand || child.compute_expand(Orientation.HORIZONTAL);
|
||||||
|
vexpand = vexpand || child.compute_expand(Orientation.VERTICAL);
|
||||||
|
child = child.get_next_sibling();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public override void measure(Orientation orientation, int for_size, out int minimum, out int natural, out int minimum_baseline, out int natural_baseline) {
|
public override void measure(Orientation orientation, int for_size, out int minimum, out int natural, out int minimum_baseline, out int natural_baseline) {
|
||||||
minimum = 0;
|
minimum = 0;
|
||||||
if (orientation == Orientation.HORIZONTAL) {
|
if (orientation == Orientation.HORIZONTAL) {
|
||||||
natural = min_natural_width;
|
natural = (int) min_natural_width;
|
||||||
} else {
|
} else {
|
||||||
natural = min_natural_height;
|
natural = (int) min_natural_height;
|
||||||
}
|
}
|
||||||
natural = int.max(0, natural);
|
|
||||||
minimum_baseline = -1;
|
minimum_baseline = -1;
|
||||||
natural_baseline = -1;
|
natural_baseline = -1;
|
||||||
Widget child = get_first_child();
|
Widget child = get_first_child();
|
||||||
@ -25,7 +35,7 @@ public class Dino.Ui.NaturalSizeIncrease : Gtk.Widget {
|
|||||||
int child_nat = 0;
|
int child_nat = 0;
|
||||||
int child_min_baseline = -1;
|
int child_min_baseline = -1;
|
||||||
int child_nat_baseline = -1;
|
int child_nat_baseline = -1;
|
||||||
child.measure(orientation, -1, out child_min, out child_nat, out child_min_baseline, out child_nat_baseline);
|
child.measure(orientation, for_size, out child_min, out child_nat, out child_min_baseline, out child_nat_baseline);
|
||||||
minimum = int.max(minimum, child_min);
|
minimum = int.max(minimum, child_min);
|
||||||
natural = int.max(natural, child_nat);
|
natural = int.max(natural, child_nat);
|
||||||
if (child_min_baseline > 0) {
|
if (child_min_baseline > 0) {
|
||||||
@ -56,4 +66,9 @@ public class Dino.Ui.NaturalSizeIncrease : Gtk.Widget {
|
|||||||
}
|
}
|
||||||
return SizeRequestMode.CONSTANT_SIZE;
|
return SizeRequestMode.CONSTANT_SIZE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public override void dispose() {
|
||||||
|
var child = this.get_first_child();
|
||||||
|
if (child != null) child.unparent();
|
||||||
|
}
|
||||||
}
|
}
|
@ -1,14 +1,23 @@
|
|||||||
namespace ICU {
|
namespace ICU {
|
||||||
|
|
||||||
[CCode (cprefix = "UCHAR_", cheader_filename = "unicode/uchar.h")]
|
[CCode (cname = "UProperty", cprefix = "UCHAR_", has_type_id = false, cheader_filename = "unicode/uchar.h")]
|
||||||
public enum Property {
|
public enum Property {
|
||||||
EMOJI,
|
EMOJI,
|
||||||
EMOJI_PRESENTATION,
|
EMOJI_PRESENTATION,
|
||||||
EMOJI_MODIFIER,
|
EMOJI_MODIFIER,
|
||||||
EMOJI_MODIFIER_BASE,
|
EMOJI_MODIFIER_BASE,
|
||||||
|
BIDI_CLASS,
|
||||||
|
}
|
||||||
|
|
||||||
|
[CCode (cname = "UCharDirection", cprefix = "U_", has_type_id = false, cheader_filename = "unicode/uchar.h")]
|
||||||
|
public enum CharDirection {
|
||||||
|
DIR_NON_SPACING_MARK,
|
||||||
}
|
}
|
||||||
|
|
||||||
[CCode (cname = "u_hasBinaryProperty", cheader_filename = "unicode/uchar.h")]
|
[CCode (cname = "u_hasBinaryProperty", cheader_filename = "unicode/uchar.h")]
|
||||||
public bool has_binary_property(unichar c, Property p);
|
public bool has_binary_property(unichar c, Property p);
|
||||||
|
|
||||||
|
[CCode (cname = "u_getIntPropertyValue", cheader_filename = "unicode/uchar.h")]
|
||||||
|
public int32 get_int_property_value(unichar c, Property p);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -117,6 +117,7 @@ public static Gee.List<Key> get_keylist(string? pattern = null, bool secret_only
|
|||||||
} catch (Error e) {
|
} catch (Error e) {
|
||||||
if (e.code != GPGError.ErrorCode.EOF) throw e;
|
if (e.code != GPGError.ErrorCode.EOF) throw e;
|
||||||
}
|
}
|
||||||
|
context.op_keylist_end();
|
||||||
return keys;
|
return keys;
|
||||||
} finally {
|
} finally {
|
||||||
global_mutex.unlock();
|
global_mutex.unlock();
|
||||||
|
@ -22,9 +22,9 @@ public class Key {
|
|||||||
public string issuer_name;
|
public string issuer_name;
|
||||||
public string chain_id;
|
public string chain_id;
|
||||||
public Validity owner_trust;
|
public Validity owner_trust;
|
||||||
[CCode(array_null_terminated = true)]
|
[CCode (array_length = false, array_null_terminated = true)]
|
||||||
public SubKey[] subkeys;
|
public SubKey[] subkeys;
|
||||||
[CCode(array_null_terminated = true)]
|
[CCode (array_length = false, array_null_terminated = true)]
|
||||||
public UserID[] uids;
|
public UserID[] uids;
|
||||||
public KeylistMode keylist_mode;
|
public KeylistMode keylist_mode;
|
||||||
// public string fpr; // requires gpgme >= 1.7.0
|
// public string fpr; // requires gpgme >= 1.7.0
|
||||||
|
@ -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,11 +117,14 @@ 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");
|
||||||
|
|
||||||
|
#if SOUP_3_0
|
||||||
|
string transfer_host = Uri.parse(http_receive_data.url, UriFlags.NONE).get_host();
|
||||||
|
head_message.accept_certificate.connect((peer_cert, errors) => { return ConnectionManager.on_invalid_certificate(transfer_host, peer_cert, errors); });
|
||||||
|
#endif
|
||||||
|
|
||||||
try {
|
try {
|
||||||
#if SOUP_3_0
|
#if SOUP_3_0
|
||||||
yield session.send_async(head_message, GLib.Priority.LOW, null);
|
yield session.send_async(head_message, GLib.Priority.LOW, null);
|
||||||
@ -150,10 +156,13 @@ 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);
|
||||||
|
|
||||||
|
#if SOUP_3_0
|
||||||
|
string transfer_host = Uri.parse(http_receive_data.url, UriFlags.NONE).get_host();
|
||||||
|
get_message.accept_certificate.connect((peer_cert, errors) => { return ConnectionManager.on_invalid_certificate(transfer_host, peer_cert, errors); });
|
||||||
|
#endif
|
||||||
|
|
||||||
try {
|
try {
|
||||||
#if SOUP_3_0
|
#if SOUP_3_0
|
||||||
InputStream stream = yield session.send_async(get_message, GLib.Priority.LOW, file_transfer.cancellable);
|
InputStream stream = yield session.send_async(get_message, GLib.Priority.LOW, file_transfer.cancellable);
|
||||||
|
@ -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,10 +93,11 @@ 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
|
||||||
|
string transfer_host = Uri.parse(file_send_data.url_up, UriFlags.NONE).get_host();
|
||||||
|
put_message.accept_certificate.connect((peer_cert, errors) => { return ConnectionManager.on_invalid_certificate(transfer_host, peer_cert, errors); });
|
||||||
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);
|
||||||
#else
|
#else
|
||||||
put_message.request_headers.set_content_type(file_meta.mime_type, null);
|
put_message.request_headers.set_content_type(file_meta.mime_type, null);
|
||||||
|
@ -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();
|
||||||
|
@ -6,6 +6,8 @@ using Xmpp.Xep;
|
|||||||
private extern const size_t NICE_ADDRESS_STRING_LEN;
|
private extern const size_t NICE_ADDRESS_STRING_LEN;
|
||||||
|
|
||||||
public class Dino.Plugins.Ice.Plugin : RootInterface, Object {
|
public class Dino.Plugins.Ice.Plugin : RootInterface, Object {
|
||||||
|
private HashMap<Account, uint> timeouts = new HashMap<Account, uint>(Account.hash_func, Account.equals_func);
|
||||||
|
|
||||||
public Dino.Application app;
|
public Dino.Application app;
|
||||||
|
|
||||||
public void registered(Dino.Application app) {
|
public void registered(Dino.Application app) {
|
||||||
@ -22,18 +24,24 @@ public class Dino.Plugins.Ice.Plugin : RootInterface, Object {
|
|||||||
stream.get_module(JingleRawUdp.Module.IDENTITY).set_local_ip_address_handler(get_local_ip_addresses);
|
stream.get_module(JingleRawUdp.Module.IDENTITY).set_local_ip_address_handler(get_local_ip_addresses);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
app.stream_interactor.stream_negotiated.connect(on_stream_negotiated);
|
app.stream_interactor.stream_negotiated.connect(external_discovery_refresh_services);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async void on_stream_negotiated(Account account, XmppStream stream) {
|
private async void external_discovery_refresh_services(Account account, XmppStream stream) {
|
||||||
Module? ice_udp_module = stream.get_module(JingleIceUdp.Module.IDENTITY) as Module;
|
Module? ice_udp_module = stream.get_module(JingleIceUdp.Module.IDENTITY) as Module;
|
||||||
if (ice_udp_module == null) return;
|
if (ice_udp_module == null) return;
|
||||||
|
|
||||||
Gee.List<Xep.ExternalServiceDiscovery.Service> services = yield ExternalServiceDiscovery.request_services(stream);
|
Gee.List<Xep.ExternalServiceDiscovery.Service> services = yield ExternalServiceDiscovery.request_services(stream);
|
||||||
foreach (Xep.ExternalServiceDiscovery.Service service in services) {
|
foreach (Xep.ExternalServiceDiscovery.Service service in services) {
|
||||||
if (service.transport == "udp" && (service.ty == "stun" || service.ty == "turn")) {
|
if (service.transport == "udp" && (service.ty == "stun" || service.ty == "turn")) {
|
||||||
InetAddress ip = yield lookup_ipv4_addess(service.host);
|
InetAddress? ip = yield lookup_ipv4_addess(service.host);
|
||||||
if (ip == null) continue;
|
if (ip == null) continue;
|
||||||
|
|
||||||
|
if (ip.is_any || ip.is_link_local || ip.is_loopback || ip.is_multicast || ip.is_site_local) {
|
||||||
|
warning("Ignoring STUN/TURN server at %s", service.host);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
if (service.ty == "stun") {
|
if (service.ty == "stun") {
|
||||||
debug("Server offers STUN server: %s:%u, resolved to %s", service.host, service.port, ip.to_string());
|
debug("Server offers STUN server: %s:%u, resolved to %s", service.host, service.port, ip.to_string());
|
||||||
ice_udp_module.stun_ip = ip.to_string();
|
ice_udp_module.stun_ip = ip.to_string();
|
||||||
@ -54,6 +62,26 @@ public class Dino.Plugins.Ice.Plugin : RootInterface, Object {
|
|||||||
ice_udp_module.stun_ip = ip.to_string();
|
ice_udp_module.stun_ip = ip.to_string();
|
||||||
ice_udp_module.stun_port = 7886;
|
ice_udp_module.stun_port = 7886;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Refresh TURN credentials before they expire
|
||||||
|
if (ice_udp_module.turn_service != null) {
|
||||||
|
DateTime? expires = ice_udp_module.turn_service.expires;
|
||||||
|
if (expires != null) {
|
||||||
|
uint refresh_delay = (uint) (expires.to_unix() - new DateTime.now_utc().to_unix()) / 2;
|
||||||
|
|
||||||
|
if (refresh_delay >= 300 && refresh_delay <= (int64) uint.MAX) { // 5 min < refetch < max
|
||||||
|
debug("Re-fetching TURN credentials in %u sec", refresh_delay);
|
||||||
|
|
||||||
|
timeouts.unset(account);
|
||||||
|
timeouts[account] = Timeout.add_seconds((uint) refresh_delay, () => {
|
||||||
|
external_discovery_refresh_services.begin(account, stream);
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
warning("TURN credentials' expiry time = %u, *not* re-fetching", refresh_delay);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void shutdown() {
|
public void shutdown() {
|
||||||
|
@ -28,9 +28,6 @@ namespace Dino.Plugins.Omemo {
|
|||||||
StanzaNode? encrypted_node = stanza.stanza.get_subnode("encrypted", NS_URI);
|
StanzaNode? encrypted_node = stanza.stanza.get_subnode("encrypted", NS_URI);
|
||||||
if (encrypted_node == null || MessageFlag.get_flag(stanza) != null || stanza.from == null) return false;
|
if (encrypted_node == null || MessageFlag.get_flag(stanza) != null || stanza.from == null) return false;
|
||||||
|
|
||||||
if (message.body == null && Xep.ExplicitEncryption.get_encryption_tag(stanza) == NS_URI) {
|
|
||||||
message.body = "[This message is OMEMO encrypted]"; // TODO temporary
|
|
||||||
}
|
|
||||||
if (!Plugin.ensure_context()) return false;
|
if (!Plugin.ensure_context()) return false;
|
||||||
int identity_id = db.identity.get_id(conversation.account.id);
|
int identity_id = db.identity.get_id(conversation.account.id);
|
||||||
|
|
||||||
@ -38,7 +35,7 @@ namespace Dino.Plugins.Omemo {
|
|||||||
stanza.add_flag(flag);
|
stanza.add_flag(flag);
|
||||||
|
|
||||||
Xep.Omemo.ParsedData? data = parse_node(encrypted_node);
|
Xep.Omemo.ParsedData? data = parse_node(encrypted_node);
|
||||||
if (data == null || data.ciphertext == null) return false;
|
if (data == null) return false;
|
||||||
|
|
||||||
|
|
||||||
foreach (Bytes encr_key in data.our_potential_encrypted_keys.keys) {
|
foreach (Bytes encr_key in data.our_potential_encrypted_keys.keys) {
|
||||||
@ -52,14 +49,16 @@ namespace Dino.Plugins.Omemo {
|
|||||||
foreach (Jid possible_jid in possible_jids) {
|
foreach (Jid possible_jid in possible_jids) {
|
||||||
try {
|
try {
|
||||||
uint8[] key = decrypt_key(data, possible_jid);
|
uint8[] key = decrypt_key(data, possible_jid);
|
||||||
|
if (data.ciphertext != null) {
|
||||||
string cleartext = arr_to_str(aes_decrypt(Cipher.AES_GCM_NOPADDING, key, data.iv, data.ciphertext));
|
string cleartext = arr_to_str(aes_decrypt(Cipher.AES_GCM_NOPADDING, key, data.iv, data.ciphertext));
|
||||||
|
message.body = cleartext;
|
||||||
|
}
|
||||||
|
|
||||||
// If we figured out which real jid a message comes from due to decryption working, save it
|
// If we figured out which real jid a message comes from due to decryption working, save it
|
||||||
if (conversation.type_ == Conversation.Type.GROUPCHAT && message.real_jid == null) {
|
if (conversation.type_ == Conversation.Type.GROUPCHAT && message.real_jid == null) {
|
||||||
message.real_jid = possible_jid;
|
message.real_jid = possible_jid;
|
||||||
}
|
}
|
||||||
|
|
||||||
message.body = cleartext;
|
|
||||||
message.encryption = Encryption.OMEMO;
|
message.encryption = Encryption.OMEMO;
|
||||||
|
|
||||||
trust_manager.message_device_id_map[message] = data.sid;
|
trust_manager.message_device_id_map[message] = data.sid;
|
||||||
@ -71,7 +70,7 @@ namespace Dino.Plugins.Omemo {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (
|
if (
|
||||||
encrypted_node.get_deep_string_content("payload") != null && // Ratchet forwarding doesn't contain payload and might not include us, which is ok
|
data.ciphertext != null && // Ratchet forwarding doesn't contain payload and might not include us, which is ok
|
||||||
data.our_potential_encrypted_keys.size == 0 && // The message was not encrypted to us
|
data.our_potential_encrypted_keys.size == 0 && // The message was not encrypted to us
|
||||||
stream_interactor.module_manager.get_module(message.account, StreamModule.IDENTITY).store.local_registration_id != data.sid // Message from this device. Never encrypted to itself.
|
stream_interactor.module_manager.get_module(message.account, StreamModule.IDENTITY).store.local_registration_id != data.sid // Message from this device. Never encrypted to itself.
|
||||||
) {
|
) {
|
||||||
|
@ -66,6 +66,7 @@ public class Manager : StreamInteractionModule, Object {
|
|||||||
this.trust_manager = trust_manager;
|
this.trust_manager = trust_manager;
|
||||||
this.encryptors = encryptors;
|
this.encryptors = encryptors;
|
||||||
|
|
||||||
|
stream_interactor.account_added.connect(on_account_added);
|
||||||
stream_interactor.stream_negotiated.connect(on_stream_negotiated);
|
stream_interactor.stream_negotiated.connect(on_stream_negotiated);
|
||||||
stream_interactor.get_module(MessageProcessor.IDENTITY).pre_message_send.connect(on_pre_message_send);
|
stream_interactor.get_module(MessageProcessor.IDENTITY).pre_message_send.connect(on_pre_message_send);
|
||||||
stream_interactor.get_module(RosterManager.IDENTITY).mutual_subscription.connect(on_mutual_subscription);
|
stream_interactor.get_module(RosterManager.IDENTITY).mutual_subscription.connect(on_mutual_subscription);
|
||||||
@ -182,6 +183,12 @@ public class Manager : StreamInteractionModule, Object {
|
|||||||
StreamModule module = stream_interactor.module_manager.get_module(account, StreamModule.IDENTITY);
|
StreamModule module = stream_interactor.module_manager.get_module(account, StreamModule.IDENTITY);
|
||||||
if (module != null) {
|
if (module != null) {
|
||||||
module.request_user_devicelist.begin(stream, account.bare_jid);
|
module.request_user_devicelist.begin(stream, account.bare_jid);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void on_account_added(Account account) {
|
||||||
|
StreamModule module = stream_interactor.module_manager.get_module(account, StreamModule.IDENTITY);
|
||||||
|
if (module != null) {
|
||||||
module.device_list_loaded.connect((jid, devices) => on_device_list_loaded(account, jid, devices));
|
module.device_list_loaded.connect((jid, devices) => on_device_list_loaded(account, jid, devices));
|
||||||
module.bundle_fetched.connect((jid, device_id, bundle) => on_bundle_fetched(account, jid, device_id, bundle));
|
module.bundle_fetched.connect((jid, device_id, bundle) => on_bundle_fetched(account, jid, device_id, bundle));
|
||||||
module.bundle_fetch_failed.connect((jid) => continue_message_sending(account, jid));
|
module.bundle_fetch_failed.connect((jid) => continue_message_sending(account, jid));
|
||||||
|
@ -117,7 +117,9 @@ public class AccountSettingsEntry : Plugins.AccountSettingsEntry {
|
|||||||
new Thread<void*> (null, () => { // Querying GnuPG might take some time
|
new Thread<void*> (null, () => { // Querying GnuPG might take some time
|
||||||
try {
|
try {
|
||||||
keys = GPGHelper.get_keylist(null, true);
|
keys = GPGHelper.get_keylist(null, true);
|
||||||
} catch (Error e) { }
|
} catch (Error e) {
|
||||||
|
warning(e.message);
|
||||||
|
}
|
||||||
Idle.add((owned)callback);
|
Idle.add((owned)callback);
|
||||||
return null;
|
return null;
|
||||||
});
|
});
|
||||||
|
@ -53,16 +53,25 @@ if(RTP_ENABLE_MSDK)
|
|||||||
set(RTP_DEFINITIONS ${RTP_DEFINITIONS} ENABLE_MSDK)
|
set(RTP_DEFINITIONS ${RTP_DEFINITIONS} ENABLE_MSDK)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
if(WebRTCAudioProcessing_VERSION GREATER "0.4")
|
if(WebRTCAudioProcessing_VERSION GREATER_EQUAL "3.0")
|
||||||
message(STATUS "Ignoring WebRTCAudioProcessing, only versions < 0.4 supported so far")
|
message(STATUS "Ignoring WebRTCAudioProcessing, only versions 0.2+, 1.* and 2.* supported so far")
|
||||||
unset(WebRTCAudioProcessing_FOUND)
|
unset(WebRTCAudioProcessing_FOUND)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
if(WebRTCAudioProcessing_FOUND)
|
if(WebRTCAudioProcessing_FOUND)
|
||||||
set(RTP_DEFINITIONS ${RTP_DEFINITIONS} WITH_VOICE_PROCESSOR)
|
set(RTP_DEFINITIONS ${RTP_DEFINITIONS} WITH_VOICE_PROCESSOR)
|
||||||
|
if(WebRTCAudioProcessing_VERSION GREATER_EQUAL "2.0")
|
||||||
|
set(RTP_VOICE_PROCESSOR_CFLAGS "-DWEBRTC2")
|
||||||
|
set(RTP_VOICE_PROCESSOR_LIB webrtc-audio-processing-2)
|
||||||
|
elseif(WebRTCAudioProcessing_VERSION GREATER_EQUAL "1.0")
|
||||||
|
set(RTP_VOICE_PROCESSOR_CFLAGS "-DWEBRTC1")
|
||||||
|
set(RTP_VOICE_PROCESSOR_LIB webrtc-audio-processing-1)
|
||||||
|
else()
|
||||||
|
set(RTP_VOICE_PROCESSOR_CFLAGS "-DWEBRTC0")
|
||||||
|
set(RTP_VOICE_PROCESSOR_LIB webrtc-audio-processing)
|
||||||
|
endif()
|
||||||
set(RTP_VOICE_PROCESSOR_VALA src/voice_processor.vala)
|
set(RTP_VOICE_PROCESSOR_VALA src/voice_processor.vala)
|
||||||
set(RTP_VOICE_PROCESSOR_CXX src/voice_processor_native.cpp)
|
set(RTP_VOICE_PROCESSOR_CXX src/voice_processor_native.cpp)
|
||||||
set(RTP_VOICE_PROCESSOR_LIB webrtc-audio-processing)
|
|
||||||
else()
|
else()
|
||||||
message(STATUS "WebRTCAudioProcessing not found, build without voice pre-processing!")
|
message(STATUS "WebRTCAudioProcessing not found, build without voice pre-processing!")
|
||||||
endif()
|
endif()
|
||||||
@ -90,7 +99,7 @@ OPTIONS
|
|||||||
${RTP_EXTRA_OPTIONS}
|
${RTP_EXTRA_OPTIONS}
|
||||||
)
|
)
|
||||||
|
|
||||||
add_definitions(${VALA_CFLAGS} -DG_LOG_DOMAIN="rtp" -I${CMAKE_CURRENT_SOURCE_DIR}/src)
|
add_definitions(${VALA_CFLAGS} -DG_LOG_DOMAIN="rtp" -I${CMAKE_CURRENT_SOURCE_DIR}/src ${RTP_VOICE_PROCESSOR_CFLAGS})
|
||||||
add_library(rtp SHARED ${RTP_VALA_C} ${RTP_VOICE_PROCESSOR_CXX} src/gst_fixes.c)
|
add_library(rtp SHARED ${RTP_VALA_C} ${RTP_VOICE_PROCESSOR_CXX} src/gst_fixes.c)
|
||||||
target_link_libraries(rtp libdino crypto-vala ${RTP_PACKAGES} gstreamer-rtp-1.0 ${RTP_VOICE_PROCESSOR_LIB})
|
target_link_libraries(rtp libdino crypto-vala ${RTP_PACKAGES} gstreamer-rtp-1.0 ${RTP_VOICE_PROCESSOR_LIB})
|
||||||
set_target_properties(rtp PROPERTIES PREFIX "")
|
set_target_properties(rtp PROPERTIES PREFIX "")
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user