diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index a04b0f33..e3a6a2b7 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -9,7 +9,7 @@ jobs: fetch-depth: 0 - run: sudo apt-get update - run: sudo apt-get remove libunwind-14-dev - - run: sudo apt-get install -y build-essential gettext cmake valac libgee-0.8-dev libsqlite3-dev libgtk-4-dev libnotify-dev libgpgme-dev libsoup2.4-dev libgcrypt20-dev libqrencode-dev 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 libsignal-protocol-c-dev - run: ./configure --with-tests --with-libsignal-in-tree - run: make - run: build/xmpp-vala-test @@ -36,4 +36,5 @@ jobs: fetch-depth: 0 - uses: flatpak/flatpak-github-actions/flatpak-builder@v6.1 with: - manifest-path: im.dino.Dino.json \ No newline at end of file + manifest-path: im.dino.Dino.json + build-bundle: false diff --git a/.gitmodules b/.gitmodules deleted file mode 100644 index b5480660..00000000 --- a/.gitmodules +++ /dev/null @@ -1,4 +0,0 @@ -[submodule "libsignal-protocol-c"] - path = plugins/signal-protocol/libsignal-protocol-c - url = https://github.com/WhisperSystems/libsignal-protocol-c.git - branch = v2.3.3 diff --git a/configure b/configure index 30b0bed5..b404fb17 100755 --- a/configure +++ b/configure @@ -1,7 +1,7 @@ #!/bin/sh OPTS=`getopt -o "h" --long \ -help,fetch-only,no-debug,disable-fast-vapi,with-tests,release,with-libsignal-in-tree,with-libsoup3,\ +help,fetch-only,no-debug,disable-fast-vapi,with-tests,release,with-libsoup3,\ enable-plugin:,disable-plugin:,\ prefix:,program-prefix:,exec-prefix:,lib-suffix:,\ bindir:,libdir:,includedir:,datadir:,\ @@ -15,13 +15,11 @@ eval set -- "$OPTS" PREFIX=${PREFIX:-/usr/local} ENABLED_PLUGINS= DISABLED_PLUGINS= -BUILD_LIBSIGNAL_IN_TREE= BUILD_TESTS= BUILD_TYPE=Debug DISABLE_FAST_VAPI= LIB_SUFFIX= NO_DEBUG= -FETCH_ONLY= USE_SOUP3= EXEC_PREFIX= @@ -55,8 +53,6 @@ Configuration: without network access later and exit. --no-debug Build without debug symbols --release Configure to build an optimized release version - --with-libsignal-in-tree Build libsignal-protocol-c in tree and link it - statically. --with-libsoup3 Build with libsoup-3.0 --with-tests Also build tests. @@ -112,11 +108,9 @@ while true; do --valac ) VALA_EXECUTABLE="$2"; shift; shift ;; --valac-flags ) VALAC_FLAGS="$2"; shift; shift ;; --lib-suffix ) LIB_SUFFIX="$2"; shift; shift ;; - --with-libsignal-in-tree ) BUILD_LIBSIGNAL_IN_TREE=yes; shift ;; --with-libsoup3 ) USE_SOUP3=yes; shift ;; --disable-fast-vapi ) DISABLE_FAST_VAPI=yes; shift ;; --no-debug ) NO_DEBUG=yes; shift ;; - --fetch-only ) FETCH_ONLY=yes; shift ;; --release ) BUILD_TYPE=RelWithDebInfo; shift ;; --with-tests ) BUILD_TESTS=yes; shift ;; # Autotools paths @@ -141,50 +135,6 @@ while true; do esac done -if [ "$BUILD_LIBSIGNAL_IN_TREE" = "yes" ] || [ "$FETCH_ONLY" = "yes" ]; then - if [ -d ".git" ]; then - git submodule update --init 2>/dev/null - else - tmp=0 - for i in $(cat .gitmodules | grep -n submodule | awk -F ':' '{print $1}') $(wc -l .gitmodules | awk '{print $1}'); do - if ! [ $tmp -eq 0 ]; then - name=$(cat .gitmodules | head -n $tmp | tail -n 1 | awk -F '"' '{print $2}') - def=$(cat .gitmodules | head -n $i | tail -n $(expr "$i" - "$tmp") | awk -F ' ' '{print $1 $2 $3}') - path=$(echo "$def" | grep '^path=' | awk -F '=' '{print $2}') - url=$(echo "$def" | grep '^url=' | awk -F '=' '{print $2}') - branch=$(echo "$def" | grep '^branch=' | awk -F '=' '{print $2}') - - if ! ls "$path"/* >/dev/null 2>/dev/null; then - git=$(which git) - if ! [ $? -eq 0 ] || ! [ -x $git ]; then - echo "Failed retrieving missing files" - exit 5 - fi - res=$(git clone "$url" "$path" 2>&1) - if ! [ $? -eq 0 ] || ! [ -d $path ]; then - echo "Failed retrieving missing files: $res" - exit 5 - fi - if [ -n "$branch" ]; then - olddir="$(pwd)" - cd "$path" - res=$(git checkout "$branch" 2>&1) - if ! [ $? -eq 0 ]; then - echo "Failed retrieving missing files: $res" - exit 5 - fi - cd "$olddir" - fi - echo "Submodule path '$path': checked out '$branch' (via git clone)" - fi - fi - tmp=$i - done - fi -fi - -if [ "$FETCH_ONLY" = "yes" ]; then exit 0; fi - if [ ! -x "$(which cmake 2>/dev/null)" ] then echo "-!- CMake required." @@ -258,7 +208,6 @@ cmake -G "$cmake_type" \ -DENABLED_PLUGINS="$ENABLED_PLUGINS" \ -DDISABLED_PLUGINS="$DISABLED_PLUGINS" \ -DBUILD_TESTS="$BUILD_TESTS" \ - -DBUILD_LIBSIGNAL_IN_TREE="$BUILD_LIBSIGNAL_IN_TREE" \ -DUSE_SOUP3="$USE_SOUP3" \ -DVALA_EXECUTABLE="$VALAC" \ -DCMAKE_VALA_FLAGS="$VALACFLAGS" \ diff --git a/im.dino.Dino.json b/im.dino.Dino.json index b2344c61..c754c88e 100644 --- a/im.dino.Dino.json +++ b/im.dino.Dino.json @@ -10,8 +10,9 @@ "--socket=wayland", "--socket=pulseaudio", "--socket=gpg-agent", + "--filesystem=xdg-run/pipewire-0", "--share=network", - "--device=all", + "--device=dri", "--talk-name=org.freedesktop.Notifications" ], "modules": [ diff --git a/libdino/src/entity/account.vala b/libdino/src/entity/account.vala index 2b7f2b04..51ceb8d1 100644 --- a/libdino/src/entity/account.vala +++ b/libdino/src/entity/account.vala @@ -18,7 +18,6 @@ public class Account : Object { public string? alias { get; set; } public bool enabled { get; set; default = false; } public string? roster_version { get; set; } - public DateTime mam_earliest_synced { get; set; default=new DateTime.from_unix_utc(0); } private Database? db; @@ -50,7 +49,6 @@ public class Account : Object { alias = row[db.account.alias]; enabled = row[db.account.enabled]; roster_version = row[db.account.roster_version]; - mam_earliest_synced = new DateTime.from_unix_utc(row[db.account.mam_earliest_synced]); notify.connect(on_update); } @@ -66,7 +64,6 @@ public class Account : Object { .value(db.account.alias, alias) .value(db.account.enabled, enabled) .value(db.account.roster_version, roster_version) - .value(db.account.mam_earliest_synced, (long)mam_earliest_synced.to_unix()) .perform(); notify.connect(on_update); @@ -106,8 +103,6 @@ public class Account : Object { update.set(db.account.enabled, enabled); break; case "roster-version": update.set(db.account.roster_version, roster_version); break; - case "mam-earliest-synced": - update.set(db.account.mam_earliest_synced, (long)mam_earliest_synced.to_unix()); break; } update.perform(); } diff --git a/libdino/src/service/database.vala b/libdino/src/service/database.vala index 6b3f5e6a..dc1d68f3 100644 --- a/libdino/src/service/database.vala +++ b/libdino/src/service/database.vala @@ -17,6 +17,7 @@ public class Database : Qlite.Database { public Column alias = new Column.Text("alias"); public Column enabled = new Column.BoolInt("enabled"); public Column roster_version = new Column.Text("roster_version") { min_version=2 }; + // no longer used. all usages already removed. remove db column at some point. public Column mam_earliest_synced = new Column.Long("mam_earliest_synced") { min_version=4 }; internal AccountTable(Database db) { diff --git a/libdino/src/service/history_sync.vala b/libdino/src/service/history_sync.vala index 2444a133..0c0571bb 100644 --- a/libdino/src/service/history_sync.vala +++ b/libdino/src/service/history_sync.vala @@ -90,11 +90,9 @@ public class Dino.HistorySync { if (!is_muc_mam && !from_our_server) return; // Get the server time of the message and store it in `mam_times` - Xmpp.MessageArchiveManagement.Flag? mam_flag = stream != null ? stream.get_flag(Xmpp.MessageArchiveManagement.Flag.IDENTITY) : null; - if (mam_flag == null) return; - string? id = message.stanza.get_deep_attribute(mam_flag.ns_ver + ":result", "id"); + string? id = message.stanza.get_deep_attribute(Xmpp.MessageArchiveManagement.NS_URI + ":result", "id"); if (id == null) return; - StanzaNode? delay_node = message.stanza.get_deep_subnode(mam_flag.ns_ver + ":result", StanzaForwarding.NS_URI + ":forwarded", DelayedDelivery.NS_URI + ":delay"); + StanzaNode? delay_node = message.stanza.get_deep_subnode(Xmpp.MessageArchiveManagement.NS_URI + ":result", StanzaForwarding.NS_URI + ":forwarded", DelayedDelivery.NS_URI + ":delay"); if (delay_node == null) { warning("MAM result did not contain delayed time %s", message.stanza.to_string()); return; @@ -104,7 +102,7 @@ public class Dino.HistorySync { mam_times[account][id] = time; // Check if this is the target message - string? query_id = message.stanza.get_deep_attribute(mam_flag.ns_ver + ":result", mam_flag.ns_ver + ":queryid"); + string? query_id = message.stanza.get_deep_attribute(Xmpp.MessageArchiveManagement.NS_URI + ":result", Xmpp.MessageArchiveManagement.NS_URI + ":queryid"); if (query_id != null && id == catchup_until_id[account]) { debug("[%s] Hitted range (id) %s", account.bare_jid.to_string(), id); hitted_range[query_id] = -2; diff --git a/libdino/src/service/message_processor.vala b/libdino/src/service/message_processor.vala index 45f06a69..04c7d1a5 100644 --- a/libdino/src/service/message_processor.vala +++ b/libdino/src/service/message_processor.vala @@ -38,7 +38,6 @@ public class MessageProcessor : StreamInteractionModule, Object { received_pipeline.connect(new FilterMessageListener()); received_pipeline.connect(new StoreMessageListener(this, stream_interactor)); received_pipeline.connect(new StoreContentItemListener(stream_interactor)); - received_pipeline.connect(new MamMessageListener(stream_interactor)); stream_interactor.account_added.connect(on_account_added); @@ -170,19 +169,21 @@ public class MessageProcessor : StreamInteractionModule, Object { XmppStream? stream = stream_interactor.get_stream(account); Xmpp.MessageArchiveManagement.MessageFlag? mam_message_flag = Xmpp.MessageArchiveManagement.MessageFlag.get_flag(message); - Xmpp.MessageArchiveManagement.Flag? mam_flag = stream != null ? stream.get_flag(Xmpp.MessageArchiveManagement.Flag.IDENTITY) : null; EntityInfo entity_info = stream_interactor.get_module(EntityInfo.IDENTITY); - if (mam_message_flag != null && mam_flag != null && mam_flag.ns_ver == Xmpp.MessageArchiveManagement.NS_URI_2 && mam_message_flag.mam_id != null) { - new_message.server_id = mam_message_flag.mam_id; + if (mam_message_flag != null && mam_message_flag.mam_id != null) { + bool server_does_mam = entity_info.has_feature_cached(account, account.bare_jid, Xmpp.MessageArchiveManagement.NS_URI); + if (server_does_mam) { + new_message.server_id = mam_message_flag.mam_id; + } } else if (message.type_ == Xmpp.MessageStanza.TYPE_GROUPCHAT) { bool server_supports_sid = (yield entity_info.has_feature(account, new_message.counterpart.bare_jid, Xep.UniqueStableStanzaIDs.NS_URI)) || - (yield entity_info.has_feature(account, new_message.counterpart.bare_jid, Xmpp.MessageArchiveManagement.NS_URI_2)); + (yield entity_info.has_feature(account, new_message.counterpart.bare_jid, Xmpp.MessageArchiveManagement.NS_URI)); if (server_supports_sid) { new_message.server_id = Xep.UniqueStableStanzaIDs.get_stanza_id(message, new_message.counterpart.bare_jid); } } else if (message.type_ == Xmpp.MessageStanza.TYPE_CHAT) { bool server_supports_sid = (yield entity_info.has_feature(account, account.bare_jid, Xep.UniqueStableStanzaIDs.NS_URI)) || - (yield entity_info.has_feature(account, account.bare_jid, Xmpp.MessageArchiveManagement.NS_URI_2)); + (yield entity_info.has_feature(account, account.bare_jid, Xmpp.MessageArchiveManagement.NS_URI)); if (server_supports_sid) { new_message.server_id = Xep.UniqueStableStanzaIDs.get_stanza_id(message, account.bare_jid); } @@ -363,29 +364,6 @@ public class MessageProcessor : StreamInteractionModule, Object { } } - private class MamMessageListener : MessageListener { - - public string[] after_actions_const = new string[]{ "DEDUPLICATE" }; - public override string action_group { get { return "MAM_NODE"; } } - public override string[] after_actions { get { return after_actions_const; } } - - private StreamInteractor stream_interactor; - - public MamMessageListener(StreamInteractor stream_interactor) { - this.stream_interactor = stream_interactor; - } - - public override async bool run(Entities.Message message, Xmpp.MessageStanza stanza, Conversation conversation) { - bool is_mam_message = Xmpp.MessageArchiveManagement.MessageFlag.get_flag(stanza) != null; - XmppStream? stream = stream_interactor.get_stream(conversation.account); - Xmpp.MessageArchiveManagement.Flag? mam_flag = stream != null ? stream.get_flag(Xmpp.MessageArchiveManagement.Flag.IDENTITY) : null; - if (is_mam_message || (mam_flag != null && mam_flag.cought_up == true)) { - conversation.account.mam_earliest_synced = message.local_time; - } - return false; - } - } - public Entities.Message create_out_message(string text, Conversation conversation) { Entities.Message message = new Entities.Message(text); message.type_ = Util.get_message_type_for_conversation(conversation); diff --git a/libdino/src/service/muc_manager.vala b/libdino/src/service/muc_manager.vala index f796c36f..e2e13435 100644 --- a/libdino/src/service/muc_manager.vala +++ b/libdino/src/service/muc_manager.vala @@ -72,7 +72,7 @@ public class MucManager : StreamInteractionModule, Object { bool receive_history = true; EntityInfo entity_info = stream_interactor.get_module(EntityInfo.IDENTITY); - bool can_do_mam = yield entity_info.has_feature(account, jid, Xmpp.MessageArchiveManagement.NS_URI_2); + bool can_do_mam = yield entity_info.has_feature(account, jid, Xmpp.MessageArchiveManagement.NS_URI); if (can_do_mam) { receive_history = false; history_since = null; diff --git a/libdino/src/service/reactions.vala b/libdino/src/service/reactions.vala index 3621dab1..9277a00f 100644 --- a/libdino/src/service/reactions.vala +++ b/libdino/src/service/reactions.vala @@ -64,7 +64,7 @@ public class Dino.Reactions : StreamInteractionModule, Object { // The MUC server needs to 1) support stable stanza ids 2) either support occupant ids or be a private room (where we know real jids) var entity_info = stream_interactor.get_module(EntityInfo.IDENTITY); bool server_supports_sid = (entity_info.has_feature_cached(conversation.account, conversation.counterpart.bare_jid, Xep.UniqueStableStanzaIDs.NS_URI)) || - (entity_info.has_feature_cached(conversation.account, conversation.counterpart.bare_jid, Xmpp.MessageArchiveManagement.NS_URI_2)); + (entity_info.has_feature_cached(conversation.account, conversation.counterpart.bare_jid, Xmpp.MessageArchiveManagement.NS_URI)); if (!server_supports_sid) return false; bool? supports_occupant_ids = entity_info.has_feature_cached(conversation.account, conversation.counterpart, Xep.OccupantIds.NS_URI); diff --git a/libdino/src/util/display_name.vala b/libdino/src/util/display_name.vala index b591eb0a..42b30600 100644 --- a/libdino/src/util/display_name.vala +++ b/libdino/src/util/display_name.vala @@ -34,6 +34,7 @@ namespace Dino { if (self_word != null && (account.alias == null || account.alias.length == 0)) { return self_word; } + if (account.alias != null && account.alias.length == 0) return null; return account.alias; } Roster.Item roster_item = stream_interactor.get_module(RosterManager.IDENTITY).get_roster_item(account, jid); diff --git a/main/data/style.css b/main/data/style.css index deac24fe..af1c58fa 100644 --- a/main/data/style.css +++ b/main/data/style.css @@ -396,17 +396,13 @@ box.dino-input-error .chat-input-status.input-status-highlight-once { text-shadow: 0 0 2px black; } -.dino-call-window .call-header-background { +.dino-call-window .participant-header-bar { background: linear-gradient(rgba(0,0,0,0.4), rgba(0,0,0,0)); border: 0; border-radius: 0; } -.dino-call-window .participant-header-bar button { - background: none; -} - -.dino-call-window .participant-header-bar button:hover { +.dino-call-window .participant-header-bar button:hover:not(.close) { background: rgba(255,255,255,0.15); border-color: rgba(255,255,255,0); box-shadow: none; diff --git a/main/src/ui/call_window/call_window.vala b/main/src/ui/call_window/call_window.vala index cf89d8e8..14b67449 100644 --- a/main/src/ui/call_window/call_window.vala +++ b/main/src/ui/call_window/call_window.vala @@ -22,7 +22,12 @@ namespace Dino.Ui { private HashMap participant_widgets = new HashMap(); private ArrayList participants = new ArrayList(); + private EventControllerFocus this_focus_events = new EventControllerFocus(); + private GestureClick this_gesture_events = new GestureClick() { touch_only=true, propagation_phase=Gtk.PropagationPhase.CAPTURE }; private EventControllerMotion this_motion_events = new EventControllerMotion(); + private double latest_motion_x = -1; + private double latest_motion_y = -1; + private const double MOTION_RELEVANCE_THRESHOLD = 2; private int own_video_width = 150; private int own_video_height = 100; @@ -54,9 +59,20 @@ namespace Dino.Ui { this.bind_property("controls-active", bottom_bar_revealer, "reveal-child", BindingFlags.SYNC_CREATE); ((Widget) this).add_controller(this_motion_events); - this_motion_events.motion.connect(reveal_control_elements); - this_motion_events.enter.connect(reveal_control_elements); - this_motion_events.leave.connect(reveal_control_elements); + this_motion_events.motion.connect((x, y) => { + if ((latest_motion_x - x).abs() <= MOTION_RELEVANCE_THRESHOLD && (latest_motion_y - y).abs() <= MOTION_RELEVANCE_THRESHOLD) return; + + latest_motion_x = x; + latest_motion_y = y; + reveal_control_elements(); + }); + + ((Widget) this).add_controller(this_focus_events); + this_focus_events.enter.connect(reveal_control_elements); + this_focus_events.leave.connect(reveal_control_elements); + + ((Widget) this).add_controller(this_gesture_events); + this_gesture_events.pressed.connect_after(reveal_control_elements); this.notify["default-width"].connect(reveal_control_elements); this.notify["default-height"].connect(reveal_control_elements); diff --git a/main/src/ui/call_window/participant_widget.vala b/main/src/ui/call_window/participant_widget.vala index 8ec1f5ea..bed64c30 100644 --- a/main/src/ui/call_window/participant_widget.vala +++ b/main/src/ui/call_window/participant_widget.vala @@ -74,14 +74,11 @@ namespace Dino.Ui { header_bar.show_title_buttons = is_highest_row; if (is_highest_row) { - header_bar.add_css_class("call-header-background"); Gtk.Settings? gtk_settings = Gtk.Settings.get_default(); if (gtk_settings != null) { string[] buttons = gtk_settings.gtk_decoration_layout.split(":"); header_bar.decoration_layout = (is_start ? buttons[0] : "") + ":" + (is_end && buttons.length == 2 ? buttons[1] : ""); } - } else { - header_bar.remove_css_class("call-header-background"); } reveal_or_hide_controls(); } diff --git a/main/src/ui/chat_input/view.vala b/main/src/ui/chat_input/view.vala index e16b4085..96986b58 100644 --- a/main/src/ui/chat_input/view.vala +++ b/main/src/ui/chat_input/view.vala @@ -39,6 +39,8 @@ public class View : Box { chooser.emoji_picked.connect((emoji) => { chat_text_view.text_view.buffer.insert_at_cursor(emoji, emoji.data.length); }); + chooser.closed.connect(do_focus); + emoji_button.set_popover(chooser); file_button.tooltip_text = Util.string_if_tooltips_active(_("Send a file")); diff --git a/plugins/rtp/src/device.vala b/plugins/rtp/src/device.vala index 1db8c996..7fee7307 100644 --- a/plugins/rtp/src/device.vala +++ b/plugins/rtp/src/device.vala @@ -215,7 +215,12 @@ public class Dino.Plugins.Rtp.Device : MediaDevice, Object { } if (new_width == active_caps_width) return; int new_height = device_caps_height * new_width / device_caps_width; - Gst.Caps new_caps = new Gst.Caps.simple("video/x-raw", "width", typeof(int), new_width, "height", typeof(int), new_height, "framerate", typeof(Gst.Fraction), device_caps_framerate_num, device_caps_framerate_den, null); + Gst.Caps new_caps; + if (device_caps_framerate_den != 0) { + new_caps = new Gst.Caps.simple("video/x-raw", "width", typeof(int), new_width, "height", typeof(int), new_height, "framerate", typeof(Gst.Fraction), device_caps_framerate_num, device_caps_framerate_den, null); + } else { + new_caps = new Gst.Caps.simple("video/x-raw", "width", typeof(int), new_width, "height", typeof(int), new_height, null); + } double required_bitrate = get_target_bitrate(new_caps); debug("Changing resolution width from %d to %d (requires bitrate %f, current target is %u)", active_caps_width, new_width, required_bitrate, bitrate); if (bitrate < required_bitrate && new_width > active_caps_width) return; @@ -347,7 +352,7 @@ public class Dino.Plugins.Rtp.Device : MediaDevice, Object { if (media == "audio") { return Gst.Caps.from_string("audio/x-raw,rate=48000,channels=1"); } else if (media == "video" && device.caps.get_size() > 0) { - int best_index = 0; + int best_index = -1; Value? best_fraction = null; int best_fps = 0; int best_width = 0; @@ -390,10 +395,25 @@ public class Dino.Plugins.Rtp.Device : MediaDevice, Object { best_fraction = best_fraction_now; } } + if (best_index == -1) { + // No caps in first round, try without framerate + for (int i = 0; i < device.caps.get_size(); i++) { + unowned Gst.Structure? that = device.caps.get_structure(i); + if (!that.has_name("video/x-raw")) continue; + int width = 0, height = 0; + if (!that.has_field("width") || !that.get_int("width", out width)) continue; + if (!that.has_field("height") || !that.get_int("height", out height)) continue; + if (best_width < width || best_width == width && best_height < height) { + best_width = width; + best_height = height; + best_index = i; + } + } + } Gst.Caps res = caps_copy_nth(device.caps, best_index); unowned Gst.Structure? that = res.get_structure(0); - Value framerate = that.get_value("framerate"); - if (framerate.type() == typeof(Gst.ValueList)) { + Value? framerate = that.get_value("framerate"); + if (framerate != null && framerate.type() == typeof(Gst.ValueList) && best_fraction != null) { that.set_value("framerate", best_fraction); } debug("Selected caps %s", res.to_string()); diff --git a/plugins/rtp/src/plugin.vala b/plugins/rtp/src/plugin.vala index ead72fe5..514ffdea 100644 --- a/plugins/rtp/src/plugin.vala +++ b/plugins/rtp/src/plugin.vala @@ -428,11 +428,11 @@ public class Dino.Plugins.Rtp.Plugin : RootInterface, VideoCallPlugin, Object { if (media == "video") { // Pick best FPS - int max_fps = 0; + int max_fps = -1; Device? max_fps_device = null; foreach (Device device in devices) { int fps = get_max_fps(device); - if (fps > max_fps) { + if (fps > max_fps || max_fps_device == null) { max_fps = fps; max_fps_device = device; } diff --git a/plugins/rtp/src/stream.vala b/plugins/rtp/src/stream.vala index abdda776..94549856 100644 --- a/plugins/rtp/src/stream.vala +++ b/plugins/rtp/src/stream.vala @@ -102,6 +102,9 @@ public class Dino.Plugins.Rtp.Stream : Xmpp.Xep.JingleRtp.Stream { send_rtp.drop = true; send_rtp.wait_on_eos = false; send_rtp.new_sample.connect(on_new_sample); +#if GST_1_20 + send_rtp.new_serialized_event.connect(on_new_event); +#endif send_rtp.connect("signal::eos", on_eos_static, this); pipe.add(send_rtp); @@ -294,6 +297,60 @@ public class Dino.Plugins.Rtp.Stream : Xmpp.Xep.JingleRtp.Stream { } } + bool flip = false; + uint8 rotation = 0; +#if GST_1_20 + private bool on_new_event(Gst.App.Sink sink) { + if (sink == null || sink != send_rtp) { + return false; + } + Gst.MiniObject obj = sink.try_pull_object(0); + if (obj.type == typeof(Gst.Event)) { + unowned Gst.TagList tags; + if (((Gst.Event)obj).type == Gst.EventType.TAG) { + ((Gst.Event)obj).parse_tag(out tags); + Gst.Video.OrientationMethod orientation_method; + Gst.Video.Orientation.from_tag(tags, out orientation_method); + switch (orientation_method) { + case Gst.Video.OrientationMethod.IDENTITY: + case Gst.Video.OrientationMethod.VERT: + default: + rotation = 0; + break; + case Gst.Video.OrientationMethod.@90R: + case Gst.Video.OrientationMethod.UL_LR: + rotation = 1; + break; + case Gst.Video.OrientationMethod.@180: + case Gst.Video.OrientationMethod.HORIZ: + rotation = 2; + break; + case Gst.Video.OrientationMethod.@90L: + case Gst.Video.OrientationMethod.UR_LL: + rotation = 3; + break; + } + switch (orientation_method) { + case Gst.Video.OrientationMethod.IDENTITY: + case Gst.Video.OrientationMethod.@90R: + case Gst.Video.OrientationMethod.@180: + case Gst.Video.OrientationMethod.@90L: + default: + flip = false; + break; + case Gst.Video.OrientationMethod.VERT: + case Gst.Video.OrientationMethod.UL_LR: + case Gst.Video.OrientationMethod.HORIZ: + case Gst.Video.OrientationMethod.UR_LL: + flip = true; + break; + } + } + } + return false; + } +#endif + private Gst.FlowReturn on_new_sample(Gst.App.Sink sink) { if (sink == null) { debug("Sink is null"); @@ -323,6 +380,24 @@ public class Dino.Plugins.Rtp.Stream : Xmpp.Xep.JingleRtp.Stream { #endif } +#if GST_1_20 + if (sink == send_rtp) { + Xmpp.Xep.JingleRtp.HeaderExtension? ext = header_extensions.first_match((it) => it.uri == "urn:3gpp:video-orientation"); + if (ext != null) { + buffer = (Gst.Buffer) buffer.make_writable(); + Gst.RTP.Buffer rtp_buffer; + if (Gst.RTP.Buffer.map(buffer, Gst.MapFlags.WRITE, out rtp_buffer)) { + uint8[] extension_data = new uint8[1]; + bool camera = false; + extension_data[0] = extension_data[0] | (rotation & 0x3); + if (flip) extension_data[0] = extension_data[0] | 0x4; + if (camera) extension_data[0] = extension_data[0] | 0x8; + rtp_buffer.add_extension_onebyte_header(ext.id, extension_data); + } + } + } +#endif + prepare_local_crypto(); uint8[] data; @@ -489,8 +564,8 @@ public class Dino.Plugins.Rtp.Stream : Xmpp.Xep.JingleRtp.Stream { } } - private uint16 previous_video_orientation_degree = uint16.MAX; - public signal void video_orientation_changed(uint16 degree); + private uint16 previous_incoming_video_orientation_degree = uint16.MAX; + public signal void incoming_video_orientation_changed(uint16 degree); public override void on_recv_rtp_data(Bytes bytes) { if (rtcp_mux && bytes.length >= 2 && bytes.get(1) >= 192 && bytes.get(1) < 224) { @@ -545,9 +620,9 @@ public class Dino.Plugins.Rtp.Stream : Xmpp.Xep.JingleRtp.Stream { case 2: rotation_degree = 180; break; case 3: rotation_degree = 270; break; } - if (rotation_degree != previous_video_orientation_degree) { - video_orientation_changed(rotation_degree); - previous_video_orientation_degree = rotation_degree; + if (rotation_degree != previous_incoming_video_orientation_degree) { + incoming_video_orientation_changed(rotation_degree); + previous_incoming_video_orientation_degree = rotation_degree; } } } @@ -723,7 +798,7 @@ public class Dino.Plugins.Rtp.VideoStream : Stream { private Gee.List outputs = new ArrayList(); private Gst.Element output_tee; private Gst.Element rotate; - private ulong video_orientation_changed_handler; + private ulong incoming_video_orientation_changed_handler; public VideoStream(Plugin plugin, Xmpp.Xep.Jingle.Content content) { base(plugin, content); @@ -731,7 +806,7 @@ public class Dino.Plugins.Rtp.VideoStream : Stream { } public override void create() { - video_orientation_changed_handler = video_orientation_changed.connect(on_video_orientation_changed); + incoming_video_orientation_changed_handler = incoming_video_orientation_changed.connect(on_video_orientation_changed); plugin.pause(); rotate = Gst.ElementFactory.make("videoflip", @"video_rotate_$rtpid"); pipe.add(rotate); @@ -780,7 +855,7 @@ public class Dino.Plugins.Rtp.VideoStream : Stream { output_tee.set_state(Gst.State.NULL); pipe.remove(output_tee); output_tee = null; - disconnect(video_orientation_changed_handler); + disconnect(incoming_video_orientation_changed_handler); } public override void add_output(Gst.Element element, Xmpp.Jid? participant) { diff --git a/plugins/rtp/src/video_widget.vala b/plugins/rtp/src/video_widget.vala index 0d66476b..20123c68 100644 --- a/plugins/rtp/src/video_widget.vala +++ b/plugins/rtp/src/video_widget.vala @@ -227,9 +227,21 @@ public class Dino.Plugins.Rtp.VideoWidget : Gtk.Widget, Dino.Plugins.VideoCallWi if (connected_device == null) return; plugin.pause(); pipe.add(sink); +#if GST_1_20 + prepare = Gst.parse_bin_from_description(@"videoflip video-direction=auto name=video_widget_$(id)_orientation ! videoflip method=horizontal-flip name=video_widget_$(id)_flip ! videoconvert name=video_widget_$(id)_convert", true); +#else prepare = Gst.parse_bin_from_description(@"videoflip method=horizontal-flip name=video_widget_$(id)_flip ! videoconvert name=video_widget_$(id)_convert", true); +#endif prepare.name = @"video_widget_$(id)_prepare"; - prepare.get_static_pad("sink").notify["caps"].connect(input_caps_changed); +#if GST_1_20 + if (prepare is Gst.Bin) { + ((Gst.Bin) prepare).get_by_name(@"video_widget_$(id)_flip").get_static_pad("sink").notify["caps"].connect(input_caps_changed); + } else { +#endif + prepare.get_static_pad("sink").notify["caps"].connect(input_caps_changed); +#if GST_1_20 + } +#endif pipe.add(prepare); connected_device_element = connected_device.link_source(); connected_device_element.link(prepare); diff --git a/plugins/signal-protocol/CMakeLists.txt b/plugins/signal-protocol/CMakeLists.txt index ea7dc9bb..b3cfae9d 100644 --- a/plugins/signal-protocol/CMakeLists.txt +++ b/plugins/signal-protocol/CMakeLists.txt @@ -28,54 +28,10 @@ GENERATE_HEADER set(C_HEADERS_SRC "") set(C_HEADERS_TARGET "") -if(NOT BUILD_LIBSIGNAL_IN_TREE) - # libsignal-protocol-c has a history of breaking compatibility on the patch level - # we'll have to check compatibility for every new release - # distro maintainers may update this dependency after compatibility tests - find_package(SignalProtocol 2.3.2 REQUIRED) -else() - add_subdirectory(libsignal-protocol-c EXCLUDE_FROM_ALL) - set_property(TARGET curve25519 PROPERTY POSITION_INDEPENDENT_CODE ON) - set_property(TARGET protobuf-c PROPERTY POSITION_INDEPENDENT_CODE ON) - set_property(TARGET signal-protocol-c PROPERTY POSITION_INDEPENDENT_CODE ON) - - set(SIGNAL_PROTOCOL_C_HEADERS - signal_protocol.h - signal_protocol_types.h - curve.h - hkdf.h - ratchet.h - protocol.h - session_state.h - session_record.h - session_pre_key.h - session_builder.h - session_cipher.h - key_helper.h - sender_key.h - sender_key_state.h - sender_key_record.h - group_session_builder.h - group_cipher.h - fingerprint.h - device_consistency.h - ) - - file(MAKE_DIRECTORY "${CMAKE_BINARY_DIR}/exports/signal") - - foreach(f ${SIGNAL_PROTOCOL_C_HEADERS}) - list(APPEND C_HEADERS_SRC "${CMAKE_CURRENT_SOURCE_DIR}/libsignal-protocol-c/src/${f}") - list(APPEND C_HEADERS_TARGET "${CMAKE_BINARY_DIR}/exports/signal/${f}") - add_custom_command(OUTPUT "${CMAKE_BINARY_DIR}/exports/signal/${f}" - COMMAND - cp "${CMAKE_CURRENT_SOURCE_DIR}/libsignal-protocol-c/src/${f}" "${CMAKE_BINARY_DIR}/exports/signal/${f}" - DEPENDS - "${CMAKE_CURRENT_SOURCE_DIR}/libsignal-protocol-c/src/${f}" - COMMENT - Copy header file signal/${f} - ) - endforeach(f) -endif() +# libsignal-protocol-c has a history of breaking compatibility on the patch level +# we'll have to check compatibility for every new release +# distro maintainers may update this dependency after compatibility tests +find_package(SignalProtocol 2.3.2 REQUIRED) list(APPEND C_HEADERS_SRC "${CMAKE_CURRENT_SOURCE_DIR}/src/signal_helper.h") list(APPEND C_HEADERS_TARGET "${CMAKE_BINARY_DIR}/exports/signal_helper.h") diff --git a/plugins/signal-protocol/libsignal-protocol-c b/plugins/signal-protocol/libsignal-protocol-c deleted file mode 160000 index 3a83a4f4..00000000 --- a/plugins/signal-protocol/libsignal-protocol-c +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 3a83a4f4ed2302ff6e68ab569c88793b50c22d28 diff --git a/xmpp-vala/src/module/xep/0313_2_message_archive_management.vala b/xmpp-vala/src/module/xep/0313_2_message_archive_management.vala index 3a6d9259..dde7d9b0 100644 --- a/xmpp-vala/src/module/xep/0313_2_message_archive_management.vala +++ b/xmpp-vala/src/module/xep/0313_2_message_archive_management.vala @@ -58,7 +58,7 @@ namespace Xmpp.MessageArchiveManagement.V2 { fields.add(field); } - return MessageArchiveManagement.create_base_query(stream, MessageArchiveManagement.NS_URI_2, mam_params.query_id, fields); + return MessageArchiveManagement.create_base_query(stream, mam_params.query_id, fields); } public async QueryResult query_archive(XmppStream stream, MamQueryParams mam_params, Cancellable? cancellable = null) { @@ -67,14 +67,14 @@ namespace Xmpp.MessageArchiveManagement.V2 { query_node.put_node(ResultSetManagement.create_set_rsm_node_before(mam_params.end_id)); } - return yield MessageArchiveManagement.query_archive(stream, MessageArchiveManagement.NS_URI_2, mam_params.mam_server, query_node, cancellable); + return yield MessageArchiveManagement.query_archive(stream, mam_params.mam_server, query_node, cancellable); } public async QueryResult page_through_results(XmppStream stream, MamQueryParams mam_params, QueryResult prev_result, Cancellable? cancellable = null) { var query_node = create_base_query(stream, mam_params); query_node.put_node(ResultSetManagement.create_set_rsm_node_before(prev_result.first)); - return yield MessageArchiveManagement.query_archive(stream, MessageArchiveManagement.NS_URI_2, mam_params.mam_server, query_node, cancellable); + return yield MessageArchiveManagement.query_archive(stream, mam_params.mam_server, query_node, cancellable); } } diff --git a/xmpp-vala/src/module/xep/0313_message_archive_management.vala b/xmpp-vala/src/module/xep/0313_message_archive_management.vala index 2235e118..1a7789cb 100644 --- a/xmpp-vala/src/module/xep/0313_message_archive_management.vala +++ b/xmpp-vala/src/module/xep/0313_message_archive_management.vala @@ -4,8 +4,6 @@ using Xmpp.Xep; namespace Xmpp.MessageArchiveManagement { public const string NS_URI = "urn:xmpp:mam:2"; -public const string NS_URI_2 = "urn:xmpp:mam:2"; -public const string NS_URI_1 = "urn:xmpp:mam:1"; public class QueryResult { public bool error { get; set; default=false; } @@ -36,43 +34,33 @@ public class Module : XmppStreamModule { private async void query_availability(XmppStream stream) { Jid own_jid = stream.get_flag(Bind.Flag.IDENTITY).my_jid.bare_jid; - - bool ver_2_available = yield stream.get_module(ServiceDiscovery.Module.IDENTITY).has_entity_feature(stream, own_jid, NS_URI); - if (ver_2_available) { - stream.add_flag(new Flag(NS_URI)); + bool mam_available = yield stream.get_module(ServiceDiscovery.Module.IDENTITY).has_entity_feature(stream, own_jid, NS_URI); + if (mam_available) { feature_available(stream); - return; - } - - bool ver_1_available = yield stream.get_module(ServiceDiscovery.Module.IDENTITY).has_entity_feature(stream, own_jid, NS_URI_1); - if (ver_1_available) { - stream.add_flag(new Flag(NS_URI_1)); - feature_available(stream); - return; } } } - internal StanzaNode create_base_query(XmppStream stream, string ns, string? queryid, Gee.List fields) { + internal StanzaNode create_base_query(XmppStream stream, string? queryid, Gee.List fields) { DataForms.DataForm data_form = new DataForms.DataForm(); DataForms.DataForm.HiddenField form_type_field = new DataForms.DataForm.HiddenField() { var="FORM_TYPE" }; - form_type_field.set_value_string(NS_VER(stream)); + form_type_field.set_value_string(NS_URI); data_form.add_field(form_type_field); foreach (var field in fields) { data_form.add_field(field); } - StanzaNode query_node = new StanzaNode.build("query", NS_VER(stream)).add_self_xmlns().put_node(data_form.get_submit_node()); + StanzaNode query_node = new StanzaNode.build("query", NS_URI).add_self_xmlns().put_node(data_form.get_submit_node()); query_node.put_attribute("queryid", queryid); return query_node; } - internal async QueryResult query_archive(XmppStream stream, string ns, Jid? mam_server, StanzaNode query_node, Cancellable? cancellable = null) { + internal async QueryResult query_archive(XmppStream stream, Jid? mam_server, StanzaNode query_node, Cancellable? cancellable = null) { var res = new QueryResult(); - Flag? flag = stream.get_flag(Flag.IDENTITY); + Flag flag = Flag.get_flag(stream); string? query_id = query_node.get_attribute("queryid"); if (flag == null || query_id == null) { res.error = true; return res; } flag.active_query_ids.add(query_id); @@ -83,7 +71,7 @@ public class Module : XmppStreamModule { Iq.Stanza result_iq = yield stream.get_module(Iq.Module.IDENTITY).send_iq_async(stream, iq, Priority.LOW, cancellable); // Parse the response IQ into a QueryResult. - StanzaNode? fin_node = result_iq.stanza.get_subnode("fin", ns); + StanzaNode? fin_node = result_iq.stanza.get_subnode("fin", NS_URI); if (fin_node == null) { res.malformed = true; return res; } StanzaNode? rsm_node = fin_node.get_subnode("set", Xmpp.ResultSetManagement.NS_URI); @@ -92,7 +80,7 @@ public class Module : XmppStreamModule { res.first = rsm_node.get_deep_string_content("first"); res.last = rsm_node.get_deep_string_content("last"); if ((res.first == null) != (res.last == null)) { res.malformed = true; return res; } - res.complete = fin_node.get_attribute_bool("complete", false, ns); + res.complete = fin_node.get_attribute_bool("complete", false, NS_URI); Idle.add(() => { flag.active_query_ids.remove(query_id); @@ -110,15 +98,14 @@ public class ReceivedPipelineListener : StanzaListener { public override string[] after_actions { get { return after_actions_const; } } public override async bool run(XmppStream stream, MessageStanza message) { - Flag? flag = stream.get_flag(Flag.IDENTITY); - if (flag == null) return false; + Flag flag = Flag.get_flag(stream); - StanzaNode? message_node = message.stanza.get_deep_subnode(NS_VER(stream) + ":result", StanzaForwarding.NS_URI + ":forwarded", Xmpp.NS_URI + ":message"); + StanzaNode? message_node = message.stanza.get_deep_subnode(NS_URI + ":result", StanzaForwarding.NS_URI + ":forwarded", Xmpp.NS_URI + ":message"); if (message_node != null) { - StanzaNode? forward_node = message.stanza.get_deep_subnode(NS_VER(stream) + ":result", StanzaForwarding.NS_URI + ":forwarded", DelayedDelivery.NS_URI + ":delay"); + StanzaNode? forward_node = message.stanza.get_deep_subnode(NS_URI + ":result", StanzaForwarding.NS_URI + ":forwarded", DelayedDelivery.NS_URI + ":delay"); DateTime? datetime = DelayedDelivery.get_time_for_node(forward_node); - string? mam_id = message.stanza.get_deep_attribute(NS_VER(stream) + ":result", NS_VER(stream) + ":id"); - string? query_id = message.stanza.get_deep_attribute(NS_VER(stream) + ":result", NS_VER(stream) + ":queryid"); + string? mam_id = message.stanza.get_deep_attribute(NS_URI + ":result", NS_URI + ":id"); + string? query_id = message.stanza.get_deep_attribute(NS_URI + ":result", NS_URI + ":queryid"); if (query_id == null) { warning("Received MAM message without queryid from %s, ignoring", message.from.to_string()); @@ -154,10 +141,14 @@ public class Flag : XmppStreamFlag { public static FlagIdentity IDENTITY = new FlagIdentity(NS_URI, "message_archive_management"); public bool cought_up { get; set; default=false; } public Gee.Set active_query_ids { get; set; default = new HashSet(); } - public string ns_ver; - public Flag(string ns_ver) { - this.ns_ver = ns_ver; + public static Flag get_flag(XmppStream stream) { + Flag? flag = stream.get_flag(Flag.IDENTITY); + if (flag == null) { + flag = new Flag(); + stream.add_flag(flag); + } + return flag; } public override string get_ns() { return NS_URI; } @@ -185,8 +176,4 @@ public class MessageFlag : Xmpp.MessageFlag { public override string get_id() { return ID; } } -private static string NS_VER(XmppStream stream) { - return stream.get_flag(Flag.IDENTITY).ns_ver; -} - } \ No newline at end of file