diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index e3a6a2b7..8408c28a 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -13,7 +13,7 @@ jobs: - run: ./configure --with-tests --with-libsignal-in-tree - run: make - run: build/xmpp-vala-test - - run: build/signal-protocol-vala-test + - run: build/omemo-test build-meson: runs-on: ubuntu-22.04 steps: @@ -22,7 +22,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 libadwaita-1-dev libgee-0.8-dev libgtk-4-dev libsqlite3-dev meson valac + - run: sudo apt-get install -y build-essential gettext libadwaita-1-dev libcanberra-dev libgcrypt20-dev libgee-0.8-dev libgpgme-dev libgstreamer-plugins-base1.0-dev libgstreamer1.0-dev libgtk-4-dev libnice-dev libnotify-dev libqrencode-dev libsignal-protocol-c-dev libsoup-3.0-dev libsqlite3-dev libsrtp2-dev libwebrtc-audio-processing-dev meson valac - run: meson setup build - run: meson compile -C build build-flatpak: diff --git a/crypto-vala/CMakeLists.txt b/crypto-vala/CMakeLists.txt index f1f3f9d7..6dec5292 100644 --- a/crypto-vala/CMakeLists.txt +++ b/crypto-vala/CMakeLists.txt @@ -14,7 +14,7 @@ SOURCES "src/random.vala" "src/srtp.vala" CUSTOM_VAPIS - "${CMAKE_CURRENT_SOURCE_DIR}/vapi/gcrypt.vapi" + "${CMAKE_CURRENT_SOURCE_DIR}/vapi/libgcrypt.vapi" "${CMAKE_CURRENT_SOURCE_DIR}/vapi/libsrtp2.vapi" PACKAGES ${CRYPTO_VALA_PACKAGES} diff --git a/crypto-vala/crypto-vala.deps b/crypto-vala/crypto-vala.deps new file mode 100644 index 00000000..c029e7af --- /dev/null +++ b/crypto-vala/crypto-vala.deps @@ -0,0 +1,2 @@ +gio-2.0 +glib-2.0 diff --git a/crypto-vala/meson.build b/crypto-vala/meson.build new file mode 100644 index 00000000..c3feb4d1 --- /dev/null +++ b/crypto-vala/meson.build @@ -0,0 +1,23 @@ +dependencies = [ + dep_gio, + dep_glib, + dep_libgcrypt, + dep_libsrtp2, +] +sources = files( + 'src/cipher.vala', + 'src/cipher_converter.vala', + 'src/error.vala', + 'src/random.vala', + 'src/srtp.vala', +) +c_args = [ + '-DG_LOG_DOMAIN="crypto-vala"', +] +vala_args = [ + '--vapidir', meson.current_source_dir() / 'vapi', +] +lib_crypto_vala = library('crypto-vala', sources, c_args: c_args, vala_args: vala_args, dependencies: dependencies, version: '0.0', install: true, install_dir: [true, true, true]) +dep_crypto_vala = declare_dependency(link_with: lib_crypto_vala, include_directories: include_directories('.')) + +install_data('crypto-vala.deps', install_dir: get_option('datadir') / 'vala/vapi') # TODO: workaround for https://github.com/mesonbuild/meson/issues/9756 diff --git a/crypto-vala/vapi/gcrypt.vapi b/crypto-vala/vapi/libgcrypt.vapi similarity index 100% rename from crypto-vala/vapi/gcrypt.vapi rename to crypto-vala/vapi/libgcrypt.vapi diff --git a/libdino/CMakeLists.txt b/libdino/CMakeLists.txt index 3c184cfd..d52f9184 100644 --- a/libdino/CMakeLists.txt +++ b/libdino/CMakeLists.txt @@ -6,6 +6,11 @@ find_packages(LIBDINO_PACKAGES REQUIRED GObject ) +set(LIBDINO_DEFINITIONS) +if(LIBDINO_VERSION VERSION_EQUAL "0.56.11") + set(LIBDINO_DEFINITIONS ${LIBDINO_DEFINITIONS} VALA_0_56_11) +endif() + vala_precompile(LIBDINO_VALA_C SOURCES src/application.vala @@ -35,6 +40,7 @@ SOURCES src/service/calls.vala src/service/chat_interaction.vala src/service/connection_manager.vala + src/service/contact_model.vala src/service/content_item_store.vala src/service/conversation_manager.vala src/service/counterpart_interaction_manager.vala @@ -76,6 +82,8 @@ GENERATE_VAPI dino GENERATE_HEADER dino +DEFINITIONS + ${LIBDINO_DEFINITIONS} ) add_custom_command(OUTPUT "${CMAKE_BINARY_DIR}/exports/dino_i18n.h" diff --git a/libdino/dino.deps b/libdino/dino.deps new file mode 100644 index 00000000..c1146392 --- /dev/null +++ b/libdino/dino.deps @@ -0,0 +1,6 @@ +gdk-pixbuf-2.0 +gee-0.8 +glib-2.0 +gmodule-2.0 +qlite +xmpp-vala diff --git a/libdino/meson.build b/libdino/meson.build index 0ebaff33..356c15d3 100644 --- a/libdino/meson.build +++ b/libdino/meson.build @@ -47,6 +47,7 @@ sources = files( 'src/service/calls.vala', 'src/service/chat_interaction.vala', 'src/service/connection_manager.vala', + 'src/service/contact_model.vala', 'src/service/content_item_store.vala', 'src/service/conversation_manager.vala', 'src/service/counterpart_interaction_manager.vala', @@ -82,5 +83,8 @@ c_args = [ '-DDINO_SYSTEM_PLUGIN_DIR="@0@"'.format(get_option('prefix') / get_option('plugindir')), '-DG_LOG_DOMAIN="libdino"', ] -lib_dino = library('dino', sources, c_args: c_args, include_directories: include_directories('src'), dependencies: dependencies) +lib_dino = library('dino', sources, c_args: c_args, include_directories: include_directories('src'), dependencies: dependencies, version: '0.0', install: true, install_dir: [true, true, true]) dep_dino = declare_dependency(link_with: lib_dino, include_directories: include_directories('.', 'src')) + +install_data('dino.deps', install_dir: get_option('datadir') / 'vala/vapi') # TODO: workaround for https://github.com/mesonbuild/meson/issues/9756 +install_headers('src/dino_i18n.h') diff --git a/libdino/src/application.vala b/libdino/src/application.vala index 5e58e364..0fcee731 100644 --- a/libdino/src/application.vala +++ b/libdino/src/application.vala @@ -39,12 +39,12 @@ public interface Application : GLib.Application { PresenceManager.start(stream_interactor); CounterpartInteractionManager.start(stream_interactor); BlockingManager.start(stream_interactor); + Calls.start(stream_interactor, db); ConversationManager.start(stream_interactor, db); MucManager.start(stream_interactor); AvatarManager.start(stream_interactor, db); RosterManager.start(stream_interactor, db); FileManager.start(stream_interactor, db); - Calls.start(stream_interactor, db); CallStore.start(stream_interactor, db); ContentItemStore.start(stream_interactor, db); ChatInteraction.start(stream_interactor); @@ -57,6 +57,7 @@ public interface Application : GLib.Application { Reactions.start(stream_interactor, db); Replies.start(stream_interactor, db); FallbackBody.start(stream_interactor, db); + ContactModels.start(stream_interactor); create_actions(); diff --git a/libdino/src/service/avatar_manager.vala b/libdino/src/service/avatar_manager.vala index c083bb2b..015e6822 100644 --- a/libdino/src/service/avatar_manager.vala +++ b/libdino/src/service/avatar_manager.vala @@ -52,7 +52,7 @@ public class AvatarManager : StreamInteractionModule, Object { if (hash == null) return null; File file = File.new_for_path(Path.build_filename(folder, hash)); if (!file.query_exists()) { - fetch_and_store_for_jid(account, jid_); + fetch_and_store_for_jid.begin(account, jid_); return null; } else { return file; @@ -169,7 +169,7 @@ public class AvatarManager : StreamInteractionModule, Object { ); foreach (var entry in get_avatar_hashes(account, Source.USER_AVATARS).entries) { - on_user_avatar_received(account, entry.key, entry.value); + on_user_avatar_received.begin(account, entry.key, entry.value); } foreach (var entry in get_avatar_hashes(account, Source.VCARD).entries) { @@ -179,7 +179,7 @@ public class AvatarManager : StreamInteractionModule, Object { continue; } - on_vcard_avatar_received(account, entry.key, entry.value); + on_vcard_avatar_received.begin(account, entry.key, entry.value); } } diff --git a/libdino/src/service/calls.vala b/libdino/src/service/calls.vala index ebaf8d03..eca7e223 100644 --- a/libdino/src/service/calls.vala +++ b/libdino/src/service/calls.vala @@ -61,8 +61,6 @@ namespace Dino { call_state.initiate_groupchat_call.begin(conversation.counterpart); } - conversation.last_active = call.time; - call_outgoing(call, call_state, conversation); 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); stream_interactor.get_module(CallStore.IDENTITY).add_call(call, conversation); - conversation.last_active = call.time; var call_state = new CallState(call, stream_interactor); 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); if (conversation == null) return null; stream_interactor.get_module(CallStore.IDENTITY).add_call(call, conversation); - conversation.last_active = call.time; CallState call_state = new CallState(call, stream_interactor); 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_); if (conversation == null) return; - conversation.last_active = call_state.call.time; if (call_state.call.direction == Call.DIRECTION_INCOMING) { call_incoming(call_state.call, call_state, conversation, video_requested, multiparty); diff --git a/libdino/src/service/contact_model.vala b/libdino/src/service/contact_model.vala new file mode 100644 index 00000000..312df4f7 --- /dev/null +++ b/libdino/src/service/contact_model.vala @@ -0,0 +1,58 @@ +using Xmpp; +using Gee; +using Qlite; + +using Dino.Entities; + +public class Dino.Model.ConversationDisplayName : Object { + public string display_name { get; set; } +} + +namespace Dino { + public class ContactModels : StreamInteractionModule, Object { + public static ModuleIdentity IDENTITY = new ModuleIdentity("contact_models"); + public string id { get { return IDENTITY.id; } } + + private StreamInteractor stream_interactor; + private HashMap conversation_models = new HashMap(Conversation.hash_func, Conversation.equals_func); + + public static void start(StreamInteractor stream_interactor) { + ContactModels m = new ContactModels(stream_interactor); + stream_interactor.add_module(m); + } + + private ContactModels(StreamInteractor stream_interactor) { + this.stream_interactor = stream_interactor; + + stream_interactor.get_module(MucManager.IDENTITY).room_info_updated.connect((account, jid) => { + check_update_models(account, jid, Conversation.Type.GROUPCHAT); + }); + stream_interactor.get_module(MucManager.IDENTITY).private_room_occupant_updated.connect((account, room, occupant) => { + check_update_models(account, room, Conversation.Type.GROUPCHAT); + }); + stream_interactor.get_module(MucManager.IDENTITY).subject_set.connect((account, jid, subject) => { + check_update_models(account, jid, Conversation.Type.GROUPCHAT); + }); + stream_interactor.get_module(RosterManager.IDENTITY).updated_roster_item.connect((account, jid, roster_item) => { + check_update_models(account, jid, Conversation.Type.CHAT); + }); + } + + private void check_update_models(Account account, Jid jid, Conversation.Type conversation_ty) { + var conversation = stream_interactor.get_module(ConversationManager.IDENTITY).get_conversation(jid, account, conversation_ty); + if (conversation == null) return; + var display_name_model = conversation_models[conversation]; + if (display_name_model == null) return; + display_name_model.display_name = Dino.get_conversation_display_name(stream_interactor, conversation, "%s (%s)"); + } + + public Model.ConversationDisplayName get_display_name_model(Conversation conversation) { + if (conversation_models.has_key(conversation)) return conversation_models[conversation]; + + var model = new Model.ConversationDisplayName(); + model.display_name = Dino.get_conversation_display_name(stream_interactor, conversation, "%s (%s)"); + conversation_models[conversation] = model; + return model; + } + } +} \ No newline at end of file diff --git a/libdino/src/service/conversation_manager.vala b/libdino/src/service/conversation_manager.vala index 59ccbac4..f966ccc7 100644 --- a/libdino/src/service/conversation_manager.vala +++ b/libdino/src/service/conversation_manager.vala @@ -29,6 +29,8 @@ public class ConversationManager : StreamInteractionModule, Object { 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).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) { @@ -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) { if (!conversations[conversation.account].has_key(conversation.counterpart)) { conversations[conversation.account][conversation.counterpart] = new ArrayList(Conversation.equals_func); diff --git a/libdino/src/service/history_sync.vala b/libdino/src/service/history_sync.vala index 0c0571bb..8ab6d7bb 100644 --- a/libdino/src/service/history_sync.vala +++ b/libdino/src/service/history_sync.vala @@ -388,9 +388,6 @@ public class Dino.HistorySync { page_result = PageResult.NoMoreMessages; } - string selection = null; - string[] selection_args = {}; - string query_id = query_params.query_id; string? after_id = query_params.start_id; diff --git a/libdino/src/service/message_processor.vala b/libdino/src/service/message_processor.vala index 01687083..baab37ce 100644 --- a/libdino/src/service/message_processor.vala +++ b/libdino/src/service/message_processor.vala @@ -167,7 +167,6 @@ public class MessageProcessor : StreamInteractionModule, Object { new_message.counterpart = counterpart_override ?? (new_message.direction == Entities.Message.DIRECTION_SENT ? message.to : message.from); new_message.ourpart = new_message.direction == Entities.Message.DIRECTION_SENT ? message.from : message.to; - XmppStream? stream = stream_interactor.get_stream(account); Xmpp.MessageArchiveManagement.MessageFlag? mam_message_flag = Xmpp.MessageArchiveManagement.MessageFlag.get_flag(message); EntityInfo entity_info = stream_interactor.get_module(EntityInfo.IDENTITY); if (mam_message_flag != null && mam_message_flag.mam_id != null) { diff --git a/libdino/src/service/muc_manager.vala b/libdino/src/service/muc_manager.vala index e2e13435..119079f0 100644 --- a/libdino/src/service/muc_manager.vala +++ b/libdino/src/service/muc_manager.vala @@ -54,6 +54,7 @@ public class MucManager : StreamInteractionModule, Object { } 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 @@ -651,6 +652,12 @@ public class MucManager : StreamInteractionModule, Object { 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) { XmppStream? stream = stream_interactor.get_stream(account); if (stream == null) return; diff --git a/libdino/src/service/stream_interactor.vala b/libdino/src/service/stream_interactor.vala index 192460d4..5d248327 100644 --- a/libdino/src/service/stream_interactor.vala +++ b/libdino/src/service/stream_interactor.vala @@ -89,7 +89,12 @@ public class ModuleIdentity : Object { } public T? cast(StreamInteractionModule module) { +#if VALA_0_56_11 + // We can't typecheck due to compiler bug + return (T) module; +#else return module.get_type().is_a(typeof(T)) ? (T?) module : null; +#endif } public bool matches(StreamInteractionModule module) { diff --git a/main/CMakeLists.txt b/main/CMakeLists.txt index abcf01ba..46f9e976 100644 --- a/main/CMakeLists.txt +++ b/main/CMakeLists.txt @@ -58,7 +58,7 @@ set(RESOURCE_LIST call_widget.ui chat_input.ui - contact_details_dialog.ui + conversation_details.ui conversation_item_widget.ui conversation_list_titlebar.ui conversation_list_titlebar_csd.ui @@ -68,6 +68,9 @@ set(RESOURCE_LIST file_send_overlay.ui global_search.ui gtk/help-overlay.ui + join_room_dialog.ui + join_room_dialog1.ui + join_room_dialog2.ui conversation_content_view/item_metadata_header.ui conversation_content_view/view.ui manage_accounts/account_row.ui @@ -86,6 +89,7 @@ set(RESOURCE_LIST unified_main_content.ui unified_window_placeholder.ui + conversation_details.css style.css style-dark.css ) @@ -111,9 +115,30 @@ endif() if(GTK4_VERSION VERSION_GREATER_EQUAL "4.8") set(MAIN_DEFINITIONS ${MAIN_DEFINITIONS} GTK_4_8) endif() +if(GTK4_VERSION VERSION_GREATER_EQUAL "4.12") + set(MAIN_DEFINITIONS ${MAIN_DEFINITIONS} GTK_4_12) +endif() if(Adwaita_VERSION VERSION_GREATER_EQUAL "1.2") set(MAIN_DEFINITIONS ${MAIN_DEFINITIONS} Adw_1_2) endif() +if(Adwaita_VERSION VERSION_GREATER_EQUAL "1.3") + set(MAIN_DEFINITIONS ${MAIN_DEFINITIONS} Adw_1_3) +endif() +if(Adwaita_VERSION VERSION_GREATER_EQUAL "1.4") + set(MAIN_DEFINITIONS ${MAIN_DEFINITIONS} Adw_1_4) +endif() +if(VALA_VERSION VERSION_GREATER_EQUAL "0.56.5" AND VALA_VERSION VERSION_LESS "0.58") + set(MAIN_DEFINITIONS ${MAIN_DEFINITIONS} VALA_0_56_GREATER_5) +endif() +if(VALA_VERSION VERSION_GREATER_EQUAL "0.56.11" AND VALA_VERSION VERSION_LESS "0.58") + set(MAIN_DEFINITIONS ${MAIN_DEFINITIONS} VALA_0_56_GREATER_11) +endif() +if(VALA_VERSION VERSION_EQUAL "0.56.11") + set(MAIN_DEFINITIONS ${MAIN_DEFINITIONS} VALA_0_56_11) +endif() +if(VALA_VERSION VERSION_EQUAL "0.56.12") + set(MAIN_DEFINITIONS ${MAIN_DEFINITIONS} VALA_0_56_12) +endif() vala_precompile(MAIN_VALA_C SOURCES @@ -172,11 +197,10 @@ SOURCES src/ui/chat_input/smiley_converter.vala src/ui/chat_input/view.vala - src/ui/contact_details/blocking_provider.vala src/ui/contact_details/settings_provider.vala src/ui/contact_details/permissions_provider.vala - src/ui/contact_details/dialog.vala - src/ui/contact_details/muc_config_form_provider.vala + + src/ui/conversation_details.vala src/ui/conversation_selector/conversation_selector.vala src/ui/conversation_selector/conversation_selector_row.vala @@ -207,6 +231,11 @@ SOURCES src/ui/widgets/date_separator.vala src/ui/widgets/fixed_ratio_picture.vala src/ui/widgets/natural_size_increase.vala + + src/view_model/conversation_details.vala + src/view_model/preferences_row.vala + + src/windows/conversation_details.vala CUSTOM_VAPIS ${CMAKE_BINARY_DIR}/exports/xmpp-vala.vapi ${CMAKE_BINARY_DIR}/exports/qlite.vapi diff --git a/main/data/contact_details_dialog.ui b/main/data/contact_details_dialog.ui deleted file mode 100644 index 4802ae9a..00000000 --- a/main/data/contact_details_dialog.ui +++ /dev/null @@ -1,110 +0,0 @@ - - - - - diff --git a/main/data/conversation_details.css b/main/data/conversation_details.css new file mode 100644 index 00000000..0eaf60c0 --- /dev/null +++ b/main/data/conversation_details.css @@ -0,0 +1,7 @@ +.extended-headerbar { + background-color: @headerbar_bg_color; +} +.extended-headerbar-end { + padding-bottom: 24px; + border-bottom: 1px solid @borders; +} \ No newline at end of file diff --git a/main/data/conversation_details.ui b/main/data/conversation_details.ui new file mode 100644 index 00000000..4229d875 --- /dev/null +++ b/main/data/conversation_details.ui @@ -0,0 +1,206 @@ + + + + + + + +
+ + Enable notifications + notification.on + + + Disable notifications + notification.off + +
+
+ + Reset to default + notification.default + +
+
+ +
+ + Notify for all messages + notification.on + + + Notify only for mentions + notification.highlight + + + Disable notifications + notification.off + +
+
+ + Reset to default + notification.default + +
+
+ \ No newline at end of file diff --git a/main/data/gresource.xml b/main/data/gresource.xml index 656defc4..503503c9 100644 --- a/main/data/gresource.xml +++ b/main/data/gresource.xml @@ -8,9 +8,9 @@ add_conversation/select_jid_fragment.ui call_widget.ui chat_input.ui - contact_details_dialog.ui conversation_content_view/item_metadata_header.ui conversation_content_view/view.ui + conversation_details.ui conversation_item_widget.ui conversation_list_titlebar.ui conversation_list_titlebar_csd.ui @@ -49,6 +49,9 @@ icons/scalable/status/dino-tick-symbolic.svg icons/scalable/status/dino-video-off-symbolic.svg icons/scalable/status/dino-video-symbolic.svg + join_room_dialog.ui + join_room_dialog1.ui + join_room_dialog2.ui manage_accounts/account_row.ui manage_accounts/add_account_dialog.ui manage_accounts/dialog.ui diff --git a/main/data/join_room_dialog.ui b/main/data/join_room_dialog.ui new file mode 100644 index 00000000..725d30e9 --- /dev/null +++ b/main/data/join_room_dialog.ui @@ -0,0 +1,44 @@ + + + + + + 500 + 600 + True + + + + False + + + channel_selection + + + + + model + + + + + + + + + confirmation + + + + + model + + + + + + + + + + \ No newline at end of file diff --git a/main/data/join_room_dialog1.ui b/main/data/join_room_dialog1.ui new file mode 100644 index 00000000..91c024d3 --- /dev/null +++ b/main/data/join_room_dialog1.ui @@ -0,0 +1,160 @@ + + + + + \ No newline at end of file diff --git a/main/data/join_room_dialog2.ui b/main/data/join_room_dialog2.ui new file mode 100644 index 00000000..1a30efc0 --- /dev/null +++ b/main/data/join_room_dialog2.ui @@ -0,0 +1,232 @@ + + + + + \ No newline at end of file diff --git a/main/data/style.css b/main/data/style.css index af1c58fa..5fe3beae 100644 --- a/main/data/style.css +++ b/main/data/style.css @@ -3,6 +3,8 @@ * It provides sane defaults for things that are very Dino-specific. */ +@import url("conversation_details.css"); + statuspage { opacity: 0.5; } diff --git a/main/meson.build b/main/meson.build index a38e15b8..ccebf67d 100644 --- a/main/meson.build +++ b/main/meson.build @@ -1,3 +1,4 @@ +subdir('po') dependencies = [ dep_dino, dep_gee, @@ -36,9 +37,6 @@ sources = files( 'src/ui/chat_input/occupants_tab_completer.vala', 'src/ui/chat_input/smiley_converter.vala', 'src/ui/chat_input/view.vala', - 'src/ui/contact_details/blocking_provider.vala', - 'src/ui/contact_details/dialog.vala', - 'src/ui/contact_details/muc_config_form_provider.vala', 'src/ui/contact_details/permissions_provider.vala', 'src/ui/contact_details/settings_provider.vala', 'src/ui/conversation_content_view/call_widget.vala', @@ -55,6 +53,7 @@ sources = files( 'src/ui/conversation_content_view/quote_widget.vala', 'src/ui/conversation_content_view/reactions_widget.vala', 'src/ui/conversation_content_view/subscription_notification.vala', + 'src/ui/conversation_details.vala', 'src/ui/conversation_list_titlebar.vala', 'src/ui/conversation_selector/conversation_selector.vala', 'src/ui/conversation_selector/conversation_selector_row.vala', @@ -89,9 +88,12 @@ sources = files( 'src/ui/widgets/date_separator.vala', 'src/ui/widgets/fixed_ratio_picture.vala', 'src/ui/widgets/natural_size_increase.vala', + 'src/view_model/conversation_details.vala', + 'src/view_model/preferences_row.vala', + 'src/windows/conversation_details.vala', ) -sources += import('gnome').compile_resources( - 'dino-resources', +sources += gnome.compile_resources( + 'resources', 'data/gresource.xml', source_dir: 'data', ) @@ -101,4 +103,22 @@ c_args = [ '-DGETTEXT_PACKAGE="dino"', '-DLOCALE_INSTALL_DIR="@0@"'.format(get_option('prefix') / get_option('localedir')), ] -exe_dino = executable('dino', sources, c_args: c_args, vala_args: ['--vapidir', meson.current_source_dir() / 'vapi'], dependencies: dependencies) +vala_args = [ + '--vapidir', meson.current_source_dir() / 'vapi', +] +if dep_libadwaita.version() == 'unknown' or dep_libadwaita.version().version_compare('>=1.2') + vala_args += ['-D', 'Adw_1_2'] +endif +if dep_gtk4.version() == 'unknown' or dep_gtk4.version().version_compare('>=4.6') + vala_args += ['-D', 'GTK_4_6'] +endif +if dep_gtk4.version() == 'unknown' or dep_gtk4.version().version_compare('>=4.8') + vala_args += ['-D', 'GTK_4_8'] +endif +exe_dino = executable('dino', sources, c_args: c_args, vala_args: vala_args, dependencies: dependencies, install: true) + +install_data('data/icons/scalable/apps/im.dino.Dino-symbolic.svg', install_dir: get_option('datadir') / 'hicolor/symbolic/apps') +install_data('data/icons/scalable/apps/im.dino.Dino.svg', install_dir: get_option('datadir') / 'hicolor/scalable/apps') +install_data('data/im.dino.Dino.appdata.xml', install_dir: get_option('datadir') / 'metainfo') +install_data('data/im.dino.Dino.desktop', install_dir: get_option('datadir') / 'applications') +install_data('data/im.dino.Dino.service', install_dir: get_option('datadir') / 'dbus-1/servces') diff --git a/main/po/meson.build b/main/po/meson.build new file mode 100644 index 00000000..ea0d12d4 --- /dev/null +++ b/main/po/meson.build @@ -0,0 +1 @@ +i18n.gettext('dino') diff --git a/main/src/ui/call_window/call_window.vala b/main/src/ui/call_window/call_window.vala index 14b67449..08ea4b21 100644 --- a/main/src/ui/call_window/call_window.vala +++ b/main/src/ui/call_window/call_window.vala @@ -253,11 +253,12 @@ namespace Dino.Ui { } private bool on_get_child_position(Widget widget, out Gdk.Rectangle allocation) { + allocation = Gdk.Rectangle(); + if (widget == own_video_box) { int width = get_size(Orientation.HORIZONTAL); int height = get_size(Orientation.VERTICAL); - allocation = Gdk.Rectangle(); allocation.width = own_video_width; allocation.height = own_video_height; allocation.x = width - own_video_width - 20; diff --git a/main/src/ui/chat_input/chat_input_controller.vala b/main/src/ui/chat_input/chat_input_controller.vala index d9608a85..d1c42d35 100644 --- a/main/src/ui/chat_input/chat_input_controller.vala +++ b/main/src/ui/chat_input/chat_input_controller.vala @@ -54,9 +54,8 @@ public class ChatInputController : Object { status_description_label.activate_link.connect((uri) => { if (uri == OPEN_CONVERSATION_DETAILS_URI){ - ContactDetails.Dialog contact_details_dialog = new ContactDetails.Dialog(stream_interactor, conversation); - contact_details_dialog.set_transient_for((Gtk.Window) chat_input.get_root()); - contact_details_dialog.present(); + var conversation_details = ConversationDetails.setup_dialog(conversation, stream_interactor, (Window)chat_input.get_root()); + conversation_details.present(); } return true; }); diff --git a/main/src/ui/contact_details/blocking_provider.vala b/main/src/ui/contact_details/blocking_provider.vala deleted file mode 100644 index 7e4a475d..00000000 --- a/main/src/ui/contact_details/blocking_provider.vala +++ /dev/null @@ -1,36 +0,0 @@ -using Gtk; - -using Dino.Entities; - -namespace Dino.Ui.ContactDetails { - -public class BlockingProvider : Plugins.ContactDetailsProvider, Object { - public string id { get { return "blocking"; } } - - private StreamInteractor stream_interactor; - - public BlockingProvider(StreamInteractor stream_interactor) { - this.stream_interactor = stream_interactor; - } - - public void populate(Conversation conversation, Plugins.ContactDetails contact_details, Plugins.WidgetType type) { - if (type != Plugins.WidgetType.GTK4) return; - if (conversation.type_ != Conversation.Type.CHAT) return; - - if (stream_interactor.get_module(BlockingManager.IDENTITY).is_supported(conversation.account)) { - bool is_blocked = stream_interactor.get_module(BlockingManager.IDENTITY).is_blocked(conversation.account, conversation.counterpart); - Switch sw = new Switch() { active=is_blocked, valign=Align.CENTER }; - sw.state_set.connect((state) => { - if (state) { - stream_interactor.get_module(BlockingManager.IDENTITY).block(conversation.account, conversation.counterpart); - } else { - stream_interactor.get_module(BlockingManager.IDENTITY).unblock(conversation.account, conversation.counterpart); - } - return false; - }); - contact_details.add(_("Settings"), _("Block"), _("Communication and status updates in either direction are blocked"), sw); - } - } -} - -} diff --git a/main/src/ui/contact_details/dialog.vala b/main/src/ui/contact_details/dialog.vala deleted file mode 100644 index c897fe4e..00000000 --- a/main/src/ui/contact_details/dialog.vala +++ /dev/null @@ -1,159 +0,0 @@ -using Gee; -using Gtk; -using Markup; -using Pango; - -using Dino.Entities; - -namespace Dino.Ui.ContactDetails { - -[GtkTemplate (ui = "/im/dino/Dino/contact_details_dialog.ui")] -public class Dialog : Gtk.Dialog { - - [GtkChild] public unowned AvatarPicture avatar; - [GtkChild] public unowned Util.EntryLabelHybrid name_hybrid; - [GtkChild] public unowned Label name_label; - [GtkChild] public unowned Label jid_label; - [GtkChild] public unowned Label account_label; - [GtkChild] public unowned Box main_box; - - private StreamInteractor stream_interactor; - private Conversation conversation; - - private Plugins.ContactDetails contact_details = new Plugins.ContactDetails(); - private HashMap categories = new HashMap(); - private Util.LabelHybridGroup hybrid_group = new Util.LabelHybridGroup(); - - construct { - name_hybrid.label.attributes = new AttrList(); - name_hybrid.label.attributes.insert(attr_weight_new(Weight.BOLD)); - } - - public Dialog(StreamInteractor stream_interactor, Conversation conversation) { - Object(use_header_bar : Util.use_csd() ? 1 : 0); - this.stream_interactor = stream_interactor; - this.conversation = conversation; - - title = conversation.type_ == Conversation.Type.GROUPCHAT ? _("Conference Details") : _("Contact Details"); - if (Util.use_csd()) { - // TODO get_header_bar directly returns a HeaderBar in vala > 0.48 - Box titles_box = new Box(Orientation.VERTICAL, 0) { valign=Align.CENTER }; - var title_label = new Label(title); - title_label.attributes = new AttrList(); - title_label.attributes.insert(Pango.attr_weight_new(Weight.BOLD)); - titles_box.append(title_label); - var subtitle_label = new Label(Util.get_conversation_display_name(stream_interactor, conversation)); - subtitle_label.attributes = new AttrList(); - subtitle_label.attributes.insert(Pango.attr_scale_new(Pango.Scale.SMALL)); - subtitle_label.add_css_class("dim-label"); - titles_box.append(subtitle_label); - - get_header_bar().set_title_widget(titles_box); - } - setup_top(); - - contact_details.add.connect(add_entry); - - Application app = GLib.Application.get_default() as Application; - app.plugin_registry.register_contact_details_entry(new SettingsProvider(stream_interactor)); - app.plugin_registry.register_contact_details_entry(new BlockingProvider(stream_interactor)); - app.plugin_registry.register_contact_details_entry(new MucConfigFormProvider(stream_interactor)); - app.plugin_registry.register_contact_details_entry(new PermissionsProvider(stream_interactor)); - - foreach (Plugins.ContactDetailsProvider provider in app.plugin_registry.contact_details_entries) { - provider.populate(conversation, contact_details, Plugins.WidgetType.GTK4); - } - - close_request.connect(() => { - contact_details.save(); - return false; - }); - } - - private void setup_top() { - if (conversation.type_ == Conversation.Type.CHAT) { - name_label.visible = false; - jid_label.margin_start = new Button().get_style_context().get_padding().left + 1; - name_hybrid.text = Util.get_conversation_display_name(stream_interactor, conversation); - close_request.connect(() => { - if (name_hybrid.text != Util.get_conversation_display_name(stream_interactor, conversation)) { - stream_interactor.get_module(RosterManager.IDENTITY).set_jid_handle(conversation.account, conversation.counterpart, name_hybrid.text); - } - return false; - }); - } else { - name_hybrid.visible = false; - name_label.label = Util.get_conversation_display_name(stream_interactor, conversation); - } - jid_label.label = conversation.counterpart.to_string(); - account_label.label = "via " + conversation.account.bare_jid.to_string(); - avatar.model = new ViewModel.CompatAvatarPictureModel(stream_interactor).set_conversation(conversation); - } - - private void add_entry(string category, string label, string? description, Object wo) { - if (!(wo is Widget)) return; - Widget w = (Widget) wo; - add_category(category); - - ListBoxRow list_row = new ListBoxRow() { activatable=false }; - Box row = new Box(Orientation.HORIZONTAL, 20) { margin_start=15, margin_end=15, margin_top=3, margin_bottom=3 }; - list_row.set_child(row); - Label label_label = new Label(label) { xalign=0, yalign=0.5f, hexpand=true }; - if (description != null && description != "") { - Box box = new Box(Orientation.VERTICAL, 0); - box.append(label_label); - Label desc_label = new Label("") { xalign=0, yalign=0.5f, hexpand=true }; - desc_label.set_markup("%s".printf(Markup.escape_text(description))); - desc_label.add_css_class("dim-label"); - box.append(desc_label); - row.append(box); - } else { - row.append(label_label); - } - - Widget widget = w; - if (widget.get_type().is_a(typeof(Entry))) { - Util.EntryLabelHybrid hybrid = new Util.EntryLabelHybrid.wrap(widget as Entry) { xalign=1 }; - hybrid_group.add(hybrid); - widget = hybrid; - } else if (widget.get_type().is_a(typeof(ComboBoxText))) { - Util.ComboBoxTextLabelHybrid hybrid = new Util.ComboBoxTextLabelHybrid.wrap(widget as ComboBoxText) { xalign=1 }; - hybrid_group.add(hybrid); - widget = hybrid; - } - widget.margin_bottom = 5; - widget.margin_top = 5; - - - row.append(widget); - categories[category].append(list_row); - - int width = get_content_area().get_width(); - int pref_height, pref_width; - get_content_area().measure(Orientation.VERTICAL, width, null, out pref_height, null, null); - default_height = pref_height + 48; - } - - private void add_category(string category) { - if (!categories.has_key(category)) { - ListBox list_box = new ListBox() { selection_mode=SelectionMode.NONE }; - categories[category] = list_box; - list_box.set_header_func((row, before_row) => { - if (row.get_header() == null && before_row != null) { - row.set_header(new Separator(Orientation.HORIZONTAL)); - } - }); - Box box = new Box(Orientation.VERTICAL, 5) { margin_top=12, margin_bottom=12 }; - Label category_label = new Label("") { xalign=0 }; - category_label.set_markup(@"$(Markup.escape_text(category))"); - box.append(category_label); - Frame frame = new Frame(null); - frame.set_child(list_box); - box.append(frame); - main_box.append(box); - } - } -} - -} - diff --git a/main/src/ui/contact_details/muc_config_form_provider.vala b/main/src/ui/contact_details/muc_config_form_provider.vala deleted file mode 100644 index 1244a759..00000000 --- a/main/src/ui/contact_details/muc_config_form_provider.vala +++ /dev/null @@ -1,93 +0,0 @@ -using Gee; -using Gtk; - -using Dino.Entities; -using Xmpp.Xep; - -namespace Dino.Ui.ContactDetails { - -public class MucConfigFormProvider : Plugins.ContactDetailsProvider, Object { - public string id { get { return "muc_config_form"; } } - private StreamInteractor stream_interactor; - - public MucConfigFormProvider(StreamInteractor stream_interactor) { - this.stream_interactor = stream_interactor; - } - - public void populate(Conversation conversation, Plugins.ContactDetails contact_details, Plugins.WidgetType type) { - if (type != Plugins.WidgetType.GTK4) return; - if (conversation.type_ == Conversation.Type.GROUPCHAT) { - Xmpp.XmppStream? stream = stream_interactor.get_stream(conversation.account); - if (stream == null) return; - - stream_interactor.get_module(MucManager.IDENTITY).get_config_form.begin(conversation.account, conversation.counterpart, (_, res) => { - DataForms.DataForm? data_form = stream_interactor.get_module(MucManager.IDENTITY).get_config_form.end(res); - if (data_form == null) return; - - for (int i = 0; i < data_form.fields.size; i++) { - DataForms.DataForm.Field field = data_form.fields[i]; - add_field(field, contact_details); - } - - string config_backup = data_form.stanza_node.to_string(); - contact_details.save.connect(() => { - // Only send the config form if something was changed - if (config_backup != data_form.stanza_node.to_string()) { - stream_interactor.get_module(MucManager.IDENTITY).set_config_form.begin(conversation.account, conversation.counterpart, data_form); - } - }); - }); - } - } - - public static void add_field(DataForms.DataForm.Field field, Plugins.ContactDetails contact_details) { - string label = field.label ?? ""; - string? desc = null; - - if (field.var != null) { - switch (field.var) { - case "muc#roomconfig_roomname": - label = _("Name of the room"); - break; - case "muc#roomconfig_roomdesc": - label = _("Description of the room"); - break; - case "muc#roomconfig_persistentroom": - label = _("Persistent"); - desc = _("The room will persist after the last occupant leaves"); - break; - case "muc#roomconfig_publicroom": - label = _("Publicly searchable"); - break; - case "muc#roomconfig_changesubject": - label = _("Occupants may change the subject"); - break; - case "muc#roomconfig_whois": - label = _("Permission to view JIDs"); - desc = _("Who is allowed to view the occupants' JIDs?"); - break; - case "muc#roomconfig_roomsecret": - label = _("Password"); - desc = _("A password to restrict access to the room"); - break; - case "muc#roomconfig_moderatedroom": - label = _("Moderated"); - desc = _("Only occupants with voice may send messages"); - break; - case "muc#roomconfig_membersonly": - label = _("Members only"); - desc = _("Only members may enter the room"); - break; - case "muc#roomconfig_historylength": - label = _("Message history"); - desc = _("Maximum amount of backlog issued by the room"); - break; - } - } - - Widget? widget = Util.get_data_form_field_widget(field); - if (widget != null) contact_details.add(_("Room Configuration"), label, desc, widget); - } -} - -} diff --git a/main/src/ui/contact_details/permissions_provider.vala b/main/src/ui/contact_details/permissions_provider.vala index ed0756e8..c7ea4a1c 100644 --- a/main/src/ui/contact_details/permissions_provider.vala +++ b/main/src/ui/contact_details/permissions_provider.vala @@ -22,7 +22,7 @@ public class PermissionsProvider : Plugins.ContactDetailsProvider, Object { if (stream_interactor.get_module(MucManager.IDENTITY).get_role(own_jid, conversation.account) == Xmpp.Xep.Muc.Role.VISITOR){ Button voice_request = new Button.with_label(_("Request")); voice_request.clicked.connect(()=>stream_interactor.get_module(MucManager.IDENTITY).request_voice(conversation.account, conversation.counterpart)); - contact_details.add(_("Permissions"), _("Request permission to send messages"), "", voice_request); + contact_details.add("Permissions", _("Request permission to send messages"), "", voice_request); } } } diff --git a/main/src/ui/contact_details/settings_provider.vala b/main/src/ui/contact_details/settings_provider.vala index 8121e5b1..6a680f64 100644 --- a/main/src/ui/contact_details/settings_provider.vala +++ b/main/src/ui/contact_details/settings_provider.vala @@ -9,8 +9,8 @@ public class SettingsProvider : Plugins.ContactDetailsProvider, Object { private StreamInteractor stream_interactor; - private string DETAILS_HEADLINE_CHAT = _("Settings"); - private string DETAILS_HEADLINE_ROOM = _("Local Settings"); + private string DETAILS_HEADLINE_CHAT = "Settings"; + private string DETAILS_HEADLINE_ROOM = "Local Settings"; public SettingsProvider(StreamInteractor stream_interactor) { this.stream_interactor = stream_interactor; @@ -33,28 +33,7 @@ public class SettingsProvider : Plugins.ContactDetailsProvider, Object { contact_details.add(DETAILS_HEADLINE_CHAT, _("Send read receipts"), "", combobox_marker); combobox_marker.active_id = get_setting_id(conversation.send_marker); combobox_marker.changed.connect(() => { conversation.send_marker = get_setting(combobox_marker.active_id); } ); - - ComboBoxText combobox_notifications = get_combobox(Dino.Application.get_default().settings.notifications); - contact_details.add(DETAILS_HEADLINE_CHAT, _("Notifications"), "", combobox_notifications); - combobox_notifications.active_id = get_notify_setting_id(conversation.notify_setting); - combobox_notifications.changed.connect(() => { conversation.notify_setting = get_notify_setting(combobox_notifications.active_id); } ); - } else if (conversation.type_ == Conversation.Type.GROUPCHAT) { - ComboBoxText combobox = new ComboBoxText(); - combobox.append("default", get_notify_setting_string(Conversation.NotifySetting.DEFAULT, conversation.get_notification_default_setting(stream_interactor))); - combobox.append("highlight", get_notify_setting_string(Conversation.NotifySetting.HIGHLIGHT)); - combobox.append("on", get_notify_setting_string(Conversation.NotifySetting.ON)); - combobox.append("off", get_notify_setting_string(Conversation.NotifySetting.OFF)); - contact_details.add(DETAILS_HEADLINE_ROOM, _("Notifications"), "", combobox); - - combobox.active_id = get_notify_setting_id(conversation.notify_setting); - combobox.changed.connect(() => { conversation.notify_setting = get_notify_setting(combobox.active_id); } ); } - - Switch pinned_switch = new Switch() { valign=Align.CENTER }; - string category = conversation.type_ == Conversation.Type.GROUPCHAT ? DETAILS_HEADLINE_ROOM : DETAILS_HEADLINE_CHAT; - contact_details.add(category, _("Pin conversation"), _("Pins the conversation to the top of the conversation list"), pinned_switch); - pinned_switch.state = conversation.pinned != 0; - pinned_switch.state_set.connect((state) => { conversation.pinned = state ? 1 : 0; return false; }); } private Conversation.Setting get_setting(string id) { @@ -69,34 +48,6 @@ public class SettingsProvider : Plugins.ContactDetailsProvider, Object { assert_not_reached(); } - private Conversation.NotifySetting get_notify_setting(string id) { - switch (id) { - case "default": - return Conversation.NotifySetting.DEFAULT; - case "on": - return Conversation.NotifySetting.ON; - case "off": - return Conversation.NotifySetting.OFF; - case "highlight": - return Conversation.NotifySetting.HIGHLIGHT; - } - assert_not_reached(); - } - - private string get_notify_setting_string(Conversation.NotifySetting setting, Conversation.NotifySetting? default_setting = null) { - switch (setting) { - case Conversation.NotifySetting.ON: - return _("On"); - case Conversation.NotifySetting.OFF: - return _("Off"); - case Conversation.NotifySetting.HIGHLIGHT: - return _("Only when mentioned"); - case Conversation.NotifySetting.DEFAULT: - return _("Default: %s").printf(get_notify_setting_string(default_setting)); - } - assert_not_reached(); - } - private string get_setting_id(Conversation.Setting setting) { switch (setting) { case Conversation.Setting.DEFAULT: @@ -109,20 +60,6 @@ public class SettingsProvider : Plugins.ContactDetailsProvider, Object { assert_not_reached(); } - private string get_notify_setting_id(Conversation.NotifySetting setting) { - switch (setting) { - case Conversation.NotifySetting.DEFAULT: - return "default"; - case Conversation.NotifySetting.ON: - return "on"; - case Conversation.NotifySetting.OFF: - return "off"; - case Conversation.NotifySetting.HIGHLIGHT: - return "highlight"; - } - assert_not_reached(); - } - private ComboBoxText get_combobox(bool default_val) { ComboBoxText combobox = new ComboBoxText(); combobox = new ComboBoxText(); diff --git a/main/src/ui/conversation_content_view/conversation_view.vala b/main/src/ui/conversation_content_view/conversation_view.vala index cc63037f..33cb3b22 100644 --- a/main/src/ui/conversation_content_view/conversation_view.vala +++ b/main/src/ui/conversation_content_view/conversation_view.vala @@ -329,7 +329,7 @@ public class ConversationView : Widget, Plugins.ConversationItemCollection, Plug do_insert_item(item); } ContentMetaItem meta_item = content_populator.get_content_meta_item(content_item); - Widget w = insert_new(meta_item); + insert_new(meta_item); content_items.add(meta_item); meta_items.add(meta_item); @@ -606,6 +606,12 @@ public class ConversationView : Widget, Plugins.ConversationItemCollection, Plug widget.dispose(); } widgets.clear(); + + Widget? notification = notifications.get_first_child(); + while (notification != null) { + notifications.remove(notification); + notification = notifications.get_first_child(); + } } private void clear_notifications() { diff --git a/main/src/ui/conversation_content_view/file_image_widget.vala b/main/src/ui/conversation_content_view/file_image_widget.vala index 4d81d762..a3579185 100644 --- a/main/src/ui/conversation_content_view/file_image_widget.vala +++ b/main/src/ui/conversation_content_view/file_image_widget.vala @@ -45,7 +45,6 @@ public class FileImageWidget : Box { image.add_controller(gesture_click_controller); FileInfo file_info = file.query_info("*", FileQueryInfoFlags.NONE); - string? mime_type = Dino.Util.get_content_type(file_info); MenuButton button = new MenuButton(); button.icon_name = "view-more"; diff --git a/main/src/ui/conversation_details.vala b/main/src/ui/conversation_details.vala new file mode 100644 index 00000000..82d6ae54 --- /dev/null +++ b/main/src/ui/conversation_details.vala @@ -0,0 +1,191 @@ +using Dino.Entities; +using Xmpp; +using Xmpp.Xep; +using Gee; +using Gtk; + +namespace Dino.Ui.ConversationDetails { + + public void populate_dialog(Model.ConversationDetails model, Conversation conversation, StreamInteractor stream_interactor) { + model.conversation = conversation; + model.display_name = stream_interactor.get_module(ContactModels.IDENTITY).get_display_name_model(conversation); + model.blocked = stream_interactor.get_module(BlockingManager.IDENTITY).is_blocked(model.conversation.account, model.conversation.counterpart); + + if (conversation.type_ == Conversation.Type.GROUPCHAT) { + stream_interactor.get_module(MucManager.IDENTITY).get_config_form.begin(conversation.account, conversation.counterpart, (_, res) => { + model.data_form = stream_interactor.get_module(MucManager.IDENTITY).get_config_form.end(res); + model.data_form_bak = model.data_form.stanza_node.to_string(); + }); + } + } + + public void bind_dialog(Model.ConversationDetails model, ViewModel.ConversationDetails view_model, StreamInteractor stream_interactor) { + view_model.avatar = new ViewModel.CompatAvatarPictureModel(stream_interactor).set_conversation(model.conversation); + view_model.show_blocked = model.conversation.type_ == Conversation.Type.CHAT && stream_interactor.get_module(BlockingManager.IDENTITY).is_supported(model.conversation.account); + + model.display_name.bind_property("display-name", view_model, "name", BindingFlags.SYNC_CREATE); + model.conversation.bind_property("notify-setting", view_model, "notification", BindingFlags.SYNC_CREATE, (_, from, ref to) => { + switch (model.conversation.get_notification_setting(stream_interactor)) { + case ON: + to = ViewModel.ConversationDetails.NotificationSetting.ON; + break; + case OFF: + to = ViewModel.ConversationDetails.NotificationSetting.OFF; + break; + case HIGHLIGHT: + to = ViewModel.ConversationDetails.NotificationSetting.HIGHLIGHT; + break; + case DEFAULT: + // A "default" setting should have been resolved to the actual default value + assert_not_reached(); + } + return true; + }); + model.conversation.bind_property("notify-setting", view_model, "notification-is-default", BindingFlags.SYNC_CREATE, (_, from, ref to) => { + var notify_setting = (Conversation.NotifySetting) from; + to = notify_setting == Conversation.NotifySetting.DEFAULT; + return true; + }); + model.conversation.bind_property("pinned", view_model, "pinned", BindingFlags.SYNC_CREATE, (_, from, ref to) => { + var from_int = (int) from; + to = from_int > 0; + return true; + }); + model.conversation.bind_property("type-", view_model, "notification-options", BindingFlags.SYNC_CREATE, (_, from, ref to) => { + var ty = (Conversation.Type) from; + to = ty == Conversation.Type.GROUPCHAT ? ViewModel.ConversationDetails.NotificationOptions.ON_HIGHLIGHT_OFF : ViewModel.ConversationDetails.NotificationOptions.ON_OFF; + return true; + }); + model.bind_property("blocked", view_model, "blocked", BindingFlags.SYNC_CREATE); + model.bind_property("data-form", view_model, "room-configuration-rows", BindingFlags.SYNC_CREATE, (_, from, ref to) => { + var data_form = (DataForms.DataForm) from; + if (data_form == null) return true; + var list_store = new GLib.ListStore(typeof(ViewModel.PreferencesRow.Any)); + + foreach (var field in data_form.fields) { + var field_view_model = Util.get_data_form_field_view_model(field); + if (field_view_model != null) { + list_store.append(field_view_model); + } + } + + to = list_store; + return true; + }); + + view_model.pin_changed.connect(() => { + model.conversation.pinned = model.conversation.pinned == 1 ? 0 : 1; + }); + view_model.block_changed.connect(() => { + if (view_model.blocked) { + stream_interactor.get_module(BlockingManager.IDENTITY).unblock(model.conversation.account, model.conversation.counterpart); + } else { + stream_interactor.get_module(BlockingManager.IDENTITY).block(model.conversation.account, model.conversation.counterpart); + } + view_model.blocked = !view_model.blocked; + }); + view_model.notification_changed.connect((setting) => { + switch (setting) { + case ON: + model.conversation.notify_setting = ON; + break; + case OFF: + model.conversation.notify_setting = OFF; + break; + case HIGHLIGHT: + model.conversation.notify_setting = HIGHLIGHT; + break; + case DEFAULT: + model.conversation.notify_setting = DEFAULT; + break; + } + }); + + view_model.notification_flipped.connect(() => { + model.conversation.notify_setting = view_model.notification == ON ? Conversation.NotifySetting.OFF : Conversation.NotifySetting.ON; + }); + } + + public Window setup_dialog(Conversation conversation, StreamInteractor stream_interactor, Window parent) { + var dialog = new Dialog() { transient_for = parent }; + var model = new Model.ConversationDetails(); + populate_dialog(model, conversation, stream_interactor); + bind_dialog(model, dialog.model, stream_interactor); + + dialog.model.about_rows.append(new ViewModel.PreferencesRow.Text() { + title = _("XMPP Address"), + text = conversation.counterpart.to_string() + }); + if (model.conversation.type_ == Conversation.Type.CHAT) { + var about_row = new ViewModel.PreferencesRow.Entry() { + title = _("Display name"), + text = dialog.model.name + }; + about_row.changed.connect(() => { + if (about_row.text != Util.get_conversation_display_name(stream_interactor, conversation)) { + stream_interactor.get_module(RosterManager.IDENTITY).set_jid_handle(conversation.account, conversation.counterpart, about_row.text); + } + }); + dialog.model.about_rows.append(about_row); + } + if (model.conversation.type_ == Conversation.Type.GROUPCHAT) { + var topic = stream_interactor.get_module(MucManager.IDENTITY).get_groupchat_subject(conversation.counterpart, conversation.account); + if (topic != null && topic != "") { + dialog.model.about_rows.append(new ViewModel.PreferencesRow.Text() { + title = _("Topic"), + text = Util.parse_add_markup(topic, null, true, true) + }); + } + } + dialog.close_request.connect(() => { + // Only send the config form if something was changed + if (model.data_form_bak != null && model.data_form_bak != model.data_form.stanza_node.to_string()) { + stream_interactor.get_module(MucManager.IDENTITY).set_config_form.begin(conversation.account, conversation.counterpart, model.data_form); + } + return false; + }); + + Plugins.ContactDetails contact_details = new Plugins.ContactDetails(); + contact_details.add.connect((c, l, d, wo) => { + add_entry(c, l, d, wo, dialog); + }); + Application app = GLib.Application.get_default() as Application; + app.plugin_registry.register_contact_details_entry(new ContactDetails.SettingsProvider(stream_interactor)); + app.plugin_registry.register_contact_details_entry(new ContactDetails.PermissionsProvider(stream_interactor)); + + foreach (Plugins.ContactDetailsProvider provider in app.plugin_registry.contact_details_entries) { + provider.populate(conversation, contact_details, Plugins.WidgetType.GTK4); + } + + return dialog; + } + + private void add_entry(string category, string label, string? description, Object wo, Dialog dialog) { + if (!(wo is Widget)) return; + + Widget widget = (Widget) wo; + if (widget.get_type().is_a(typeof(Entry))) { + Util.EntryLabelHybrid hybrid = new Util.EntryLabelHybrid.wrap(widget as Entry) { xalign=1 }; + widget = hybrid; + } else if (widget.get_type().is_a(typeof(ComboBoxText))) { + Util.ComboBoxTextLabelHybrid hybrid = new Util.ComboBoxTextLabelHybrid.wrap(widget as ComboBoxText) { xalign=1 }; + widget = hybrid; + } + + var view_model = new ViewModel.PreferencesRow.WidgetDeprecated() { + title = label, + widget = widget + }; + + switch (category) { + case "Encryption": + dialog.model.encryption_rows.append(view_model); + break; + case "Permissions": + case "Local Settings": + case "Settings": + dialog.model.settings_rows.append(view_model); + break; + } + } +} \ No newline at end of file diff --git a/main/src/ui/conversation_selector/conversation_selector.vala b/main/src/ui/conversation_selector/conversation_selector.vala index 535a61b0..d16ef3ee 100644 --- a/main/src/ui/conversation_selector/conversation_selector.vala +++ b/main/src/ui/conversation_selector/conversation_selector.vala @@ -14,7 +14,6 @@ public class ConversationSelector : Widget { ListBox list_box = new ListBox() { hexpand=true }; private StreamInteractor stream_interactor; - private uint? drag_timeout; private HashMap rows = new HashMap(Conversation.hash_func, Conversation.equals_func); public ConversationSelector init(StreamInteractor stream_interactor) { diff --git a/main/src/ui/conversation_titlebar/menu_entry.vala b/main/src/ui/conversation_titlebar/menu_entry.vala index d0b9fbcd..479a228c 100644 --- a/main/src/ui/conversation_titlebar/menu_entry.vala +++ b/main/src/ui/conversation_titlebar/menu_entry.vala @@ -34,9 +34,8 @@ class MenuEntry : Plugins.ConversationTitlebarEntry, Object { } private void on_clicked() { - ContactDetails.Dialog contact_details_dialog = new ContactDetails.Dialog(stream_interactor, conversation); - contact_details_dialog.set_transient_for((Window) button.get_root()); - contact_details_dialog.present(); + var conversation_details = ConversationDetails.setup_dialog(conversation, stream_interactor, (Window)button.get_root()); + conversation_details.present(); } public Object? get_widget(Plugins.WidgetType type) { diff --git a/main/src/ui/notifier_freedesktop.vala b/main/src/ui/notifier_freedesktop.vala index 3f390102..a1df5990 100644 --- a/main/src/ui/notifier_freedesktop.vala +++ b/main/src/ui/notifier_freedesktop.vala @@ -273,23 +273,28 @@ public class Dino.Ui.FreeDesktopNotifier : NotificationProvider, Object { } public async void retract_content_item_notifications() { - if (content_notifications != null) { - foreach (uint32 id in content_notifications.values) { - try { - dbus_notifications.close_notification.begin(id); - } catch (Error e) { } - } - content_notifications.clear(); + foreach (uint32 id in content_notifications.values) { + try { + dbus_notifications.close_notification.begin(id); + } catch (Error e) { } } + content_notifications.clear(); } 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]); - } 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) { diff --git a/main/src/ui/notifier_gnotifications.vala b/main/src/ui/notifier_gnotifications.vala index 90c8ca8c..4d36620d 100644 --- a/main/src/ui/notifier_gnotifications.vala +++ b/main/src/ui/notifier_gnotifications.vala @@ -109,6 +109,8 @@ namespace Dino.Ui { case ConnectionManager.ConnectionError.Source.TLS: notification.set_body("Invalid TLS certificate"); break; + default: + break; } GLib.Application.get_default().send_notification(account.id.to_string() + "-connection-error", notification); } diff --git a/main/src/ui/occupant_menu/list.vala b/main/src/ui/occupant_menu/list.vala index b9a4a74f..ce5a1981 100644 --- a/main/src/ui/occupant_menu/list.vala +++ b/main/src/ui/occupant_menu/list.vala @@ -153,7 +153,6 @@ public class List : Box { if (affiliation1 < affiliation2) return -1; else if (affiliation1 > affiliation2) return 1; else return row_wrapper1.name_label.label.collate(row_wrapper2.name_label.label); - return 0; } private int get_affiliation_ranking(Xmpp.Xep.Muc.Affiliation affiliation) { diff --git a/main/src/ui/util/data_forms.vala b/main/src/ui/util/data_forms.vala index 1f598025..39dce3ee 100644 --- a/main/src/ui/util/data_forms.vala +++ b/main/src/ui/util/data_forms.vala @@ -6,6 +6,99 @@ using Xmpp.Xep; namespace Dino.Ui.Util { +public static ViewModel.PreferencesRow.Any? get_data_form_field_view_model(DataForms.DataForm.Field field) { + if (field.type_ == null) return null; + + ViewModel.PreferencesRow.Any? view_model = null; + + string? label = null; + string? desc = null; + + if (field.var != null) { + switch (field.var) { + case "muc#roomconfig_roomname": + label = _("Name of the room"); + break; + case "muc#roomconfig_roomdesc": + label = _("Description of the room"); + break; + case "muc#roomconfig_persistentroom": + label = _("Persistent"); + desc = _("The room will persist after the last occupant leaves"); + break; + case "muc#roomconfig_publicroom": + label = _("Publicly searchable"); + break; + case "muc#roomconfig_changesubject": + label = _("Occupants may change the subject"); + break; + case "muc#roomconfig_whois": + label = _("Permission to view JIDs"); + desc = _("Who is allowed to view the occupants' JIDs?"); + break; + case "muc#roomconfig_roomsecret": + label = _("Password"); +// desc = _("A password to restrict access to the room"); + break; + case "muc#roomconfig_moderatedroom": + label = _("Moderated"); + desc = _("Only occupants with voice may send messages"); + break; + case "muc#roomconfig_membersonly": + label = _("Members only"); + desc = _("Only members may enter the room"); + break; +// case "muc#roomconfig_historylength": +// label = _("Message history"); +// desc = _("Maximum amount of backlog issued by the room"); +// break; + } + } + + if (label == null) label = field.label; + + switch (field.type_) { + case DataForms.DataForm.Type.BOOLEAN: + DataForms.DataForm.BooleanField boolean_field = field as DataForms.DataForm.BooleanField; + var toggle_model = new ViewModel.PreferencesRow.Toggle() { subtitle = desc, state = boolean_field.value }; + boolean_field.bind_property("value", toggle_model, "state", BindingFlags.SYNC_CREATE | BindingFlags.BIDIRECTIONAL); + view_model = toggle_model; + break; + case DataForms.DataForm.Type.JID_MULTI: + return null; + case DataForms.DataForm.Type.LIST_SINGLE: + DataForms.DataForm.ListSingleField list_single_field = field as DataForms.DataForm.ListSingleField; + var combobox_model = new ViewModel.PreferencesRow.ComboBox(); + for (int i = 0; i < list_single_field.options.size; i++) { + DataForms.DataForm.Option option = list_single_field.options[i]; + combobox_model.items.add(option.label); + if (option.value == list_single_field.value) combobox_model.active_item = i; + } + combobox_model.bind_property("active-item", list_single_field, "value", BindingFlags.DEFAULT, (binding, from, ref to) => { + var active_item = (int) from; + to = list_single_field.options[active_item].value; + return true; + }); + view_model = combobox_model; + break; + case DataForms.DataForm.Type.LIST_MULTI: + return null; + case DataForms.DataForm.Type.TEXT_PRIVATE: + return null; + case DataForms.DataForm.Type.TEXT_SINGLE: + DataForms.DataForm.TextSingleField text_single_field = field as DataForms.DataForm.TextSingleField; + var entry_model = new ViewModel.PreferencesRow.Entry() { text = text_single_field.value }; + text_single_field.bind_property("value", entry_model, "text", BindingFlags.SYNC_CREATE | BindingFlags.BIDIRECTIONAL); + view_model = entry_model; + break; + default: + return null; + } + + view_model.title = label; + return view_model; +} + public static Widget? get_data_form_field_widget(DataForms.DataForm.Field field) { if (field.type_ == null) return null; switch (field.type_) { diff --git a/main/src/ui/util/helper.vala b/main/src/ui/util/helper.vala index d6da72dd..63288fc2 100644 --- a/main/src/ui/util/helper.vala +++ b/main/src/ui/util/helper.vala @@ -103,7 +103,13 @@ private const string force_color_css = "%s { color: %s; }"; public static Gtk.CssProvider force_css(Gtk.Widget widget, string css) { var p = new Gtk.CssProvider(); try { +#if GTK_4_12 && (VALA_0_56_GREATER_11 || VALA_0_58) + p.load_from_string(css); +#elif (VALA_0_56_11 || VALA_0_56_12) + p.load_from_data(css, css.length); +#else p.load_from_data(css.data); +#endif widget.get_style_context().add_provider(p, Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION); } catch (GLib.Error err) { // handle err diff --git a/main/src/ui/util/label_hybrid.vala b/main/src/ui/util/label_hybrid.vala index f426de7e..0059d2ae 100644 --- a/main/src/ui/util/label_hybrid.vala +++ b/main/src/ui/util/label_hybrid.vala @@ -97,10 +97,6 @@ public class EntryLabelHybrid : LabelHybrid { } } - private void on_focus_leave() { - show_label(); - } - private void update_label() { if (visibility) { label.label = entry.text; diff --git a/main/src/ui/widgets/avatar_picture.vala b/main/src/ui/widgets/avatar_picture.vala index e632413c..fb254915 100644 --- a/main/src/ui/widgets/avatar_picture.vala +++ b/main/src/ui/widgets/avatar_picture.vala @@ -454,7 +454,7 @@ public class Dino.Ui.AvatarPicture : Gtk.Widget { label.insert_after(this, null); label.attributes = new Pango.AttrList(); label.attributes.insert(Pango.attr_foreground_new(uint16.MAX, uint16.MAX, uint16.MAX)); -#if GTK_4_8 && VALA_0_58 +#if GTK_4_8 && (VALA_0_56_GREATER_5 || VALA_0_58) picture.content_fit = Gtk.ContentFit.COVER; #elif GTK_4_8 picture.@set("content-fit", 2); @@ -516,4 +516,4 @@ public class Dino.Ui.AvatarPicture : Gtk.Widget { base.snapshot(snapshot); } } -} \ No newline at end of file +} diff --git a/main/src/ui/widgets/fixed_ratio_picture.vala b/main/src/ui/widgets/fixed_ratio_picture.vala index 79c60141..3e83ec0c 100644 --- a/main/src/ui/widgets/fixed_ratio_picture.vala +++ b/main/src/ui/widgets/fixed_ratio_picture.vala @@ -8,7 +8,7 @@ class Dino.Ui.FixedRatioPicture : Gtk.Widget { public int max_height { get; set; default = int.MAX; } public File file { get { return inner.file; } set { inner.file = value; } } public Gdk.Paintable paintable { get { return inner.paintable; } set { inner.paintable = value; } } -#if GTK_4_8 && VALA_0_58 +#if GTK_4_8 && (VALA_0_56_GREATER_5 || VALA_0_58) public Gtk.ContentFit content_fit { get { return inner.content_fit; } set { inner.content_fit = value; } } #endif private Gtk.Picture inner = new Gtk.Picture(); @@ -85,4 +85,4 @@ class Dino.Ui.FixedRatioPicture : Gtk.Widget { inner.unparent(); base.dispose(); } -} \ No newline at end of file +} diff --git a/main/src/view_model/conversation_details.vala b/main/src/view_model/conversation_details.vala new file mode 100644 index 00000000..15bf7535 --- /dev/null +++ b/main/src/view_model/conversation_details.vala @@ -0,0 +1,49 @@ +using Dino.Entities; +using Xmpp; +using Xmpp.Xep; +using Gee; +using Gtk; + +public class Dino.Ui.ViewModel.ConversationDetails : Object { + public signal void pin_changed(); + public signal void block_changed(); + public signal void notification_flipped(); + public signal void notification_changed(NotificationSetting setting); + + public enum NotificationOptions { + ON_OFF, + ON_HIGHLIGHT_OFF + } + + public enum NotificationSetting { + DEFAULT, + ON, + HIGHLIGHT, + OFF + } + + public ViewModel.CompatAvatarPictureModel avatar { get; set; } + public string name { get; set; } + public bool pinned { get; set; } + + public NotificationSetting notification { get; set; } + public NotificationOptions notification_options { get; set; } + public bool notification_is_default { get; set; } + + public bool show_blocked { get; set; } + public bool blocked { get; set; } + + public GLib.ListStore preferences_rows = new GLib.ListStore(typeof(PreferencesRow.Any)); + public GLib.ListStore about_rows = new GLib.ListStore(typeof(PreferencesRow.Any)); + public GLib.ListStore encryption_rows = new GLib.ListStore(typeof(PreferencesRow.Any)); + public GLib.ListStore settings_rows = new GLib.ListStore(typeof(PreferencesRow.Any)); + public GLib.ListStore room_configuration_rows { get; set; } +} + +public class Dino.Ui.Model.ConversationDetails : Object { + public Conversation conversation { get; set; } + public Dino.Model.ConversationDisplayName display_name { get; set; } + public DataForms.DataForm? data_form { get; set; } + public string? data_form_bak; + public bool blocked { get; set; } +} \ No newline at end of file diff --git a/main/src/view_model/preferences_row.vala b/main/src/view_model/preferences_row.vala new file mode 100644 index 00000000..3a04ee1e --- /dev/null +++ b/main/src/view_model/preferences_row.vala @@ -0,0 +1,34 @@ +using Dino.Entities; +using Xmpp; +using Xmpp.Xep; +using Gee; +using Gtk; + +namespace Dino.Ui.ViewModel.PreferencesRow { + public abstract class Any : Object { + public string title { get; set; } + } + + public class Text : Any { + public string text { get; set; } + } + + public class Entry : Any { + public signal void changed(); + public string text { get; set; } + } + + public class Toggle : Any { + public string subtitle { get; set; } + public bool state { get; set; } + } + + public class ComboBox : Any { + public Gee.List items = new ArrayList(); + public int active_item { get; set; } + } + + public class WidgetDeprecated : Any { + public Widget widget; + } +} \ No newline at end of file diff --git a/main/src/windows/conversation_details.vala b/main/src/windows/conversation_details.vala new file mode 100644 index 00000000..099412d1 --- /dev/null +++ b/main/src/windows/conversation_details.vala @@ -0,0 +1,232 @@ +using Dino.Entities; +using Xmpp; +using Xmpp.Xep; +using Gee; +using Gtk; + +namespace Dino.Ui.ConversationDetails { + + [GtkTemplate (ui = "/im/dino/Dino/conversation_details.ui")] + public class Dialog : Adw.Window { + [GtkChild] public unowned Box about_box; + [GtkChild] public unowned Button pin_button; + [GtkChild] public unowned Adw.ButtonContent pin_button_content; + [GtkChild] public unowned Button block_button; + [GtkChild] public unowned Adw.ButtonContent block_button_content; + [GtkChild] public unowned Button notification_button_toggle; + [GtkChild] public unowned Adw.ButtonContent notification_button_toggle_content; + [GtkChild] public unowned MenuButton notification_button_menu; + [GtkChild] public unowned Adw.ButtonContent notification_button_menu_content; + [GtkChild] public unowned Adw.SplitButton notification_button_split; + [GtkChild] public unowned Adw.ButtonContent notification_button_split_content; + + [GtkChild] public unowned ViewModel.ConversationDetails model { get; } + + class construct { + install_action("notification.on", null, (widget, action_name) => { ((Dialog) widget).model.notification_changed(ViewModel.ConversationDetails.NotificationSetting.ON); } ); + install_action("notification.off", null, (widget, action_name) => { ((Dialog) widget).model.notification_changed(ViewModel.ConversationDetails.NotificationSetting.OFF); } ); + install_action("notification.highlight", null, (widget, action_name) => { ((Dialog) widget).model.notification_changed(ViewModel.ConversationDetails.NotificationSetting.HIGHLIGHT); } ); + install_action("notification.default", null, (widget, action_name) => { ((Dialog) widget).model.notification_changed(ViewModel.ConversationDetails.NotificationSetting.DEFAULT); } ); + } + + construct { + pin_button.clicked.connect(() => { model.pin_changed(); }); + block_button.clicked.connect(() => { model.block_changed(); }); + notification_button_toggle.clicked.connect(() => { model.notification_flipped(); }); + notification_button_split.clicked.connect(() => { model.notification_flipped(); }); + + model.notify["pinned"].connect(update_pinned_button); + model.notify["blocked"].connect(update_blocked_button); + model.notify["notification"].connect(update_notification_button); + model.notify["notification"].connect(update_notification_button_state); + model.notify["notification-options"].connect(update_notification_button_visibility); + model.notify["notification-is-default"].connect(update_notification_button_visibility); + + model.about_rows.items_changed.connect(create_preferences_rows); + model.encryption_rows.items_changed.connect(create_preferences_rows); + model.settings_rows.items_changed.connect(create_preferences_rows); + model.notify["room-configuration-rows"].connect(create_preferences_rows); + +#if Adw_1_4 + // TODO: replace with putting buttons in new line on small screens + notification_button_menu_content.can_shrink = true; +#endif + } + + private void update_pinned_button() { + pin_button_content.icon_name = "view-pin-symbolic"; + pin_button_content.label = model.pinned ? _("Pinned") : _("Pin"); + if (model.pinned) { + pin_button.add_css_class("accent"); + } else { + pin_button.remove_css_class("accent"); + } + } + + private void update_blocked_button() { + block_button_content.icon_name = "action-unavailable-symbolic"; + block_button_content.label = model.blocked ? _("Blocked") : _("Block"); + if (model.blocked) { + block_button.add_css_class("error"); + } else { + block_button.remove_css_class("error"); + } + } + + private void update_notification_button() { + string icon_name = model.notification == OFF ? + "notifications-disabled-symbolic" : "notification-symbolic"; + notification_button_toggle_content.icon_name = icon_name; + notification_button_split_content.icon_name = icon_name; + notification_button_menu_content.icon_name = icon_name; + } + + private void update_notification_button_state() { + switch (model.notification) { + case ON: + notification_button_toggle_content.label = _("Mute"); + notification_button_split_content.label = _("Mute"); + notification_button_menu_content.label = _("Notifications enabled"); + break; + case HIGHLIGHT: + notification_button_menu_content.label = _("Notifications for mentions"); + break; + case OFF: + notification_button_toggle_content.label = _("Muted"); + notification_button_split_content.label = _("Muted"); + notification_button_menu_content.label = _("Notifications disabled"); + break; + } + } + + private void update_notification_button_visibility() { + notification_button_toggle.visible = notification_button_menu.visible = notification_button_split.visible = false; + + if (model.notification_options == ON_OFF) { + if (model.notification_is_default) { + notification_button_toggle.visible = true; + } else { + notification_button_split.visible = true; + } + } else { + notification_button_menu.visible = true; + } + } + + private void create_preferences_rows() { + var widget = about_box.get_first_child(); + while (widget != null) { + about_box.remove(widget); + widget = about_box.get_first_child(); + } + + if (model.about_rows.get_n_items() > 0) { + about_box.append(rows_to_preference_group(model.about_rows, _("About"))); + } + if (model.encryption_rows.get_n_items() > 0) { + about_box.append(rows_to_preference_group(model.encryption_rows, _("Encryption"))); + } + if (model.settings_rows.get_n_items() > 0) { + about_box.append(rows_to_preference_group(model.settings_rows, _("Settings"))); + } + if (model.room_configuration_rows != null && model.room_configuration_rows.get_n_items() > 0) { + about_box.append(rows_to_preference_group(model.room_configuration_rows, _("Room Configuration"))); + } + } + + private Adw.PreferencesGroup rows_to_preference_group(GLib.ListStore row_view_models, string title) { + var preference_group = new Adw.PreferencesGroup() { title=title }; + + for (int preference_group_i = 0; preference_group_i < row_view_models.get_n_items(); preference_group_i++) { + var preferences_row = (ViewModel.PreferencesRow.Any) row_view_models.get_item(preference_group_i); + + Widget? w = null; + + var entry_view_model = preferences_row as ViewModel.PreferencesRow.Entry; + if (entry_view_model != null) { +#if Adw_1_2 + Adw.EntryRow view = new Adw.EntryRow() { title = entry_view_model.title, show_apply_button=true }; + entry_view_model.bind_property("text", view, "text", BindingFlags.SYNC_CREATE | BindingFlags.BIDIRECTIONAL, (_, from, ref to) => { + var str = (string) from; + to = str ?? ""; + return true; + }); + view.apply.connect(() => { + entry_view_model.changed(); + }); +#else + var view = new Adw.ActionRow() { title = entry_view_model.title }; + var entry = new Entry() { text=entry_view_model.text, valign=Align.CENTER }; + entry_view_model.bind_property("text", entry, "text", BindingFlags.SYNC_CREATE | BindingFlags.BIDIRECTIONAL); + entry.changed.connect(() => { + entry_view_model.changed(); + }); + view.activatable_widget = entry; + view.add_suffix(entry); +#endif + w = view; + } + + var row_text = preferences_row as ViewModel.PreferencesRow.Text; + if (row_text != null) { + w = new Adw.ActionRow() { + title = row_text.title, + subtitle = row_text.text, +#if Adw_1_3 + subtitle_selectable = true +#endif + }; + w.add_css_class("property"); + + Util.force_css(w, "row.property > box.header > box.title > .title { font-weight: 400; font-size: 9pt; opacity: 0.55; }"); + Util.force_css(w, "row.property > box.header > box.title > .subtitle { font-size: inherit; opacity: 1; }"); + } + + var toggle_view_model = preferences_row as ViewModel.PreferencesRow.Toggle; + if (toggle_view_model != null) { + var view = new Adw.ActionRow() { title = toggle_view_model.title, subtitle = toggle_view_model.subtitle }; + var toggle = new Switch() { valign = Align.CENTER }; + view.activatable_widget = toggle; + view.add_suffix(toggle); + toggle_view_model.bind_property("state", toggle, "active", BindingFlags.SYNC_CREATE | BindingFlags.BIDIRECTIONAL); + w = view; + } + + var combobox_view_model = preferences_row as ViewModel.PreferencesRow.ComboBox; + if (combobox_view_model != null) { + var string_list = new StringList(null); + foreach (string text in combobox_view_model.items) { + string_list.append(text); + } +#if Adw_1_4 + var view = new Adw.ComboRow() { title = combobox_view_model.title }; + view.model = string_list; + combobox_view_model.bind_property("active-item", view, "selected", BindingFlags.SYNC_CREATE | BindingFlags.BIDIRECTIONAL); +#else + var view = new Adw.ActionRow() { title = combobox_view_model.title }; + var drop_down = new DropDown(string_list, null) { valign = Align.CENTER }; + combobox_view_model.bind_property("active-item", drop_down, "selected", BindingFlags.SYNC_CREATE | BindingFlags.BIDIRECTIONAL); + view.activatable_widget = drop_down; + view.add_suffix(drop_down); +#endif + w = view; + } + + var widget_view_model = preferences_row as ViewModel.PreferencesRow.WidgetDeprecated; + if (widget_view_model != null) { + var view = new Adw.ActionRow() { title = widget_view_model.title }; + view.add_suffix(widget_view_model.widget); + w = view; + } + + if (w == null) { + continue; + } + + preference_group.add(w); + } + + return preference_group; + } + } +} \ No newline at end of file diff --git a/meson.build b/meson.build index aea22d57..4ad18477 100644 --- a/meson.build +++ b/meson.build @@ -1,18 +1,58 @@ -project('xmpp-vala', 'vala') +project('xmpp-vala', 'c', 'cpp', 'vala') fs = import('fs') +gnome = import('gnome') +i18n = import('i18n') python = import('python') +# plugin_crypto is enabled if any of the crypto plugins is enabled, auto if +# none of them are explicitly enabled but at least one is set to auto, or +# disabled if all of them are disabled. +plugin_crypto = get_option('plugin-ice') +foreach plugin : ['plugin-ice', 'plugin-omemo', 'plugin-rtp'] + if get_option(plugin).enabled() and not plugin_crypto.enabled() + plugin_crypto = get_option(plugin) + elif get_option(plugin).allowed() and not plugin_crypto.allowed() + plugin_crypto = get_option(plugin) + endif +endforeach + +if get_option('plugin-ice').enabled() and not get_option('plugin-rtp').enabled() + dep_gnutls_required = get_option('plugin-ice') +elif get_option('plugin-ice').allowed() and not get_option('plugin-rtp').allowed() + dep_gnutls_required = get_option('plugin-ice') +else + dep_gnutls_required = get_option('plugin-rtp') +endif + dep_gdk_pixbuf = dependency('gdk-pixbuf-2.0') dep_gee = dependency('gee-0.8') dep_gio = dependency('gio-2.0') dep_glib = dependency('glib-2.0') +dep_gnutls = dependency('gnutls', disabler: true, required: dep_gnutls_required) dep_gmodule = dependency('gmodule-2.0') +dep_gpgme = dependency('gpgme', disabler: true, required: get_option('plugin-openpgp')) +dep_gstreamer = dependency('gstreamer-1.0', disabler: true, required: get_option('plugin-rtp')) +dep_gstreamer_app = dependency('gstreamer-app-1.0', disabler: true, required: get_option('plugin-rtp')) +dep_gstreamer_audio = dependency('gstreamer-audio-1.0', disabler: true, required: get_option('plugin-rtp')) +dep_gstreamer_rtp = dependency('gstreamer-rtp-1.0', disabler: true, required: get_option('plugin-rtp')) +dep_gstreamer_video = dependency('gstreamer-video-1.0', disabler: true, required: get_option('plugin-rtp')) dep_gtk4 = dependency('gtk4') dep_icu_uc = dependency('icu-uc') dep_libadwaita = dependency('libadwaita-1') +dep_libcanberra = dependency('libcanberra', disabler: true, required: get_option('plugin-notification-sound')) +dep_libgcrypt = dependency('libgcrypt', disabler: true, required: plugin_crypto) +dep_libqrencode = dependency('libqrencode', disabler: true, required: get_option('plugin-omemo')) +dep_libsrtp2 = dependency('libsrtp2', disabler: true, required: plugin_crypto) +# 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 +dep_libsignal_protocol_c = dependency('libsignal-protocol-c', version: ['>=2.3.2', '<2.3.4'], disabler: true, required: get_option('plugin-omemo')) +dep_libsoup = dependency('libsoup-3.0', disabler: true, required: get_option('plugin-http-files')) +dep_nice = dependency('nice', version: '>=0.1.15', disabler: true, required: get_option('plugin-ice')) dep_m = meson.get_compiler('c').find_library('m', required: false) dep_sqlite3 = dependency('sqlite3', version: '>=3.24') +dep_webrtc_audio_processing = dependency('webrtc-audio-processing', version: ['>=0.2', '<0.4'], required: get_option('plugin-rtp-webrtc-audio-processing')) prog_git = find_program('git', required: false) prog_python = python.find_installation() @@ -21,3 +61,5 @@ subdir('qlite') subdir('xmpp-vala') subdir('libdino') subdir('main') +subdir('crypto-vala') +subdir('plugins') diff --git a/meson_options.txt b/meson_options.txt index 6e47b7c8..caee3093 100644 --- a/meson_options.txt +++ b/meson_options.txt @@ -1 +1,14 @@ -option('plugindir', type: 'string', value: 'lib/dino/plugins', description: 'Plugin directory for Dino plugins') +option('plugindir', type: 'string', value: 'lib/dino/plugins', description: 'Dino plugin directory') + +option('plugin-http-files', type: 'feature', description: 'HTTP file upload') +option('plugin-ice', type: 'feature', description: '') +option('plugin-notification-sound', type: 'feature', description: 'Sound for chat notifications') +option('plugin-omemo', type: 'feature', description: 'End-to-end encryption') +option('plugin-openpgp', type: 'feature', description: 'End-to-end encryption using PGP') +option('plugin-rtp', type: 'feature', description: 'Voice/video calls') + +option('plugin-rtp-h264', type: 'feature', value: 'disabled', description: 'H264 codec') +option('plugin-rtp-msdk', type: 'feature', value: 'disabled', description: 'Intel MediaSDK') +option('plugin-rtp-vaapi', type: 'feature', value: 'disabled', description: 'Video Acceleration API') +option('plugin-rtp-vp9', type: 'feature', value: 'disabled', description: 'VP9 codec') +option('plugin-rtp-webrtc-audio-processing', type: 'feature', description: 'Voice preprocessing') diff --git a/plugins/CMakeLists.txt b/plugins/CMakeLists.txt index d2c965ad..9b8dd8dc 100644 --- a/plugins/CMakeLists.txt +++ b/plugins/CMakeLists.txt @@ -3,11 +3,5 @@ if(WIN32) endif(WIN32) foreach(plugin ${PLUGINS}) - if ("omemo" STREQUAL ${plugin}) - add_subdirectory(signal-protocol) - endif () - if ("openpgp" STREQUAL ${plugin}) - add_subdirectory(gpgme-vala) - endif () add_subdirectory(${plugin}) endforeach(plugin) diff --git a/plugins/gpgme-vala/CMakeLists.txt b/plugins/gpgme-vala/CMakeLists.txt deleted file mode 100644 index 5255bac4..00000000 --- a/plugins/gpgme-vala/CMakeLists.txt +++ /dev/null @@ -1,52 +0,0 @@ -find_package(GPGME REQUIRED) -find_packages(GPGME_VALA_PACKAGES REQUIRED - Gee - GLib - GObject -) - -vala_precompile(GPGME_VALA_C -SOURCES - "src/gpgme_helper.vala" -CUSTOM_VAPIS - "${CMAKE_CURRENT_SOURCE_DIR}/vapi/gpgme.vapi" - "${CMAKE_CURRENT_SOURCE_DIR}/vapi/gpgme_public.vapi" - "${CMAKE_CURRENT_SOURCE_DIR}/vapi/gpg-error.vapi" -PACKAGES - ${GPGME_VALA_PACKAGES} -GENERATE_VAPI - gpgme-vala -GENERATE_HEADER - gpgme-vala -) - -add_custom_command(OUTPUT "${CMAKE_BINARY_DIR}/exports/gpgme_fix.h" -COMMAND - cp "${CMAKE_CURRENT_SOURCE_DIR}/src/gpgme_fix.h" "${CMAKE_BINARY_DIR}/exports/gpgme_fix.h" -DEPENDS - "${CMAKE_CURRENT_SOURCE_DIR}/src/gpgme_fix.h" -COMMENT - Copy header file gpgme_fix.h -) - -add_custom_command(OUTPUT ${CMAKE_BINARY_DIR}/exports/gpgme.vapi -COMMAND - cat "${CMAKE_BINARY_DIR}/exports/gpgme-vala.vapi" "${CMAKE_CURRENT_SOURCE_DIR}/vapi/gpgme_public.vapi" > "${CMAKE_BINARY_DIR}/exports/gpgme.vapi" -DEPENDS - ${CMAKE_BINARY_DIR}/exports/gpgme-vala.vapi - ${CMAKE_CURRENT_SOURCE_DIR}/vapi/gpgme_public.vapi -) - -add_custom_target(gpgme-vapi -DEPENDS - ${CMAKE_BINARY_DIR}/exports/gpgme_fix.h - ${CMAKE_BINARY_DIR}/exports/gpgme.vapi -) - -set(CFLAGS ${VALA_CFLAGS} -I${CMAKE_CURRENT_SOURCE_DIR}/src) -add_definitions(${CFLAGS}) -add_library(gpgme-vala STATIC ${GPGME_VALA_C} src/gpgme_fix.c) -add_dependencies(gpgme-vala gpgme-vapi) -target_link_libraries(gpgme-vala ${GPGME_VALA_PACKAGES} gpgme) -set_property(TARGET gpgme-vala PROPERTY POSITION_INDEPENDENT_CODE ON) - diff --git a/plugins/gpgme-vala/vapi/gpgme.deps b/plugins/gpgme-vala/vapi/gpgme.deps deleted file mode 100644 index a0f4f82b..00000000 --- a/plugins/gpgme-vala/vapi/gpgme.deps +++ /dev/null @@ -1 +0,0 @@ -gpg-error diff --git a/plugins/gpgme-vala/vapi/gpgme_public.vapi b/plugins/gpgme-vala/vapi/gpgme_public.vapi deleted file mode 100644 index 5dbe023e..00000000 --- a/plugins/gpgme-vala/vapi/gpgme_public.vapi +++ /dev/null @@ -1,165 +0,0 @@ -[CCode (lower_case_cprefix = "gpgme_", cheader_filename = "gpgme.h,gpgme_fix.h")] -namespace GPG { - -[CCode (cname = "gpgme_check_version")] -public unowned string check_version(string? required_version = null); - -[CCode (cname = "gpgme_set_global_flag")] -public int set_global_flag(string name, string value); - -[Compact] -[CCode (cname = "struct _gpgme_key", ref_function = "gpgme_key_ref_vapi", unref_function = "gpgme_key_unref_vapi", free_function = "gpgme_key_release")] -public class Key { - public bool revoked; - public bool expired; - public bool disabled; - public bool invalid; - public bool can_encrypt; - public bool can_sign; - public bool can_certify; - public bool can_authenticate; - public bool is_qualified; - public bool secret; - public Protocol protocol; - public string issuer_serial; - public string issuer_name; - public string chain_id; - public Validity owner_trust; - [CCode(array_null_terminated = true)] - public SubKey[] subkeys; - [CCode(array_null_terminated = true)] - public UserID[] uids; - public KeylistMode keylist_mode; - // public string fpr; // requires gpgme >= 1.7.0 - public string fpr { get { return subkeys[0].fpr; } } -} - -[CCode (cname = "struct _gpgme_user_id")] -public struct UserID { - UserID* next; - - bool revoked; - bool invalid; - Validity validity; - string uid; - string name; - string email; - string comment; - KeySig signatures; -} - -[CCode (cname = "struct _gpgme_key_sig")] -public struct KeySig { - KeySig* next; - bool invoked; - bool expired; - bool invalid; - bool exportable; - PublicKeyAlgorithm algo; - string keyid; - long timestamp; - long expires; -// GPGError.Error status; - string uid; - string name; - string email; - string comment; - uint sig_class; - SigNotation notations; -} - -[CCode (cname = "struct _gpgme_subkey")] -public struct SubKey { - SubKey* next; - bool revoked; - bool expired; - bool disabled; - bool invalid; - bool can_encrypt; - bool can_sign; - bool can_certify; - bool secret; - bool can_authenticate; - bool is_qualified; - bool is_cardkey; - PublicKeyAlgorithm algo; - uint length; - string keyid; - - string fpr; - long timestamp; - long expires; - string? cardnumber; -} - -[CCode (cname = "struct _gpgme_sig_notation")] -public struct SigNotation { - SigNotation* next; - string? name; - string value; - int name_len; - int value_len; - SigNotationFlags flags; - bool human_readable; - bool critical; -} - -[CCode (cname = "gpgme_sig_notation_flags_t", cprefix = "GPGME_SIG_NOTATION_")] -public enum SigNotationFlags { - HUMAN_READABLE, - CRITICAL -} - -[CCode (cname = "gpgme_sig_mode_t", cprefix = "GPGME_SIG_MODE_")] -public enum SigMode { - NORMAL, - DETACH, - CLEAR -} - -[CCode (cname = "gpgme_encrypt_flags_t", cprefix = "GPGME_ENCRYPT_")] -public enum EncryptFlags { - ALWAYS_TRUST, - NO_ENCRYPT_TO -} - -[CCode (cname = "gpgme_pubkey_algo_t", cprefix = "GPGME_PK_")] -public enum PublicKeyAlgorithm { - RSA, - RSA_E, - RSA_S, - ELG_E, - DSA, - ELG -} - -[CCode (cname = "gpgme_protocol_t", cprefix = "GPGME_PROTOCOL_")] -public enum Protocol { - OpenPGP, - CMS, - GPGCONF, - ASSUAN, - UNKNOWN -} - -[CCode (cname = "gpgme_keylist_mode_t", cprefix = "GPGME_KEYLIST_MODE_")] -public enum KeylistMode { - LOCAL, - EXTERN, - SIGS, - SIG_NOTATIONS, - EPHEMERAL, - VALIDATE -} - -[CCode (cname = "gpgme_validity_t", cprefix = "GPGME_VALIDITY_")] -public enum Validity { - UNKNOWN, - UNDEFINED, - NEVER, - MARGINAL, - FULL, - ULTIMATE -} - -} \ No newline at end of file diff --git a/plugins/http-files/meson.build b/plugins/http-files/meson.build new file mode 100644 index 00000000..6b0f3820 --- /dev/null +++ b/plugins/http-files/meson.build @@ -0,0 +1,22 @@ +dependencies = [ + dep_dino, + dep_gee, + dep_glib, + dep_gmodule, + dep_gtk4, + dep_libsoup, + dep_qlite, + dep_xmpp_vala, +] +sources = files( + 'src/file_provider.vala', + 'src/file_sender.vala', + 'src/plugin.vala', + 'src/register_plugin.vala', +) + +vala_args = [ + '--define=SOUP_3_0', +] +lib_http_files = shared_library('http-files', sources, name_prefix: '', vala_args: vala_args, dependencies: dependencies, install: true, install_dir: get_option('libdir') / 'dino/plugins') +dep_http_files = declare_dependency(link_with: lib_http_files, include_directories: include_directories('.')) diff --git a/plugins/ice/meson.build b/plugins/ice/meson.build new file mode 100644 index 00000000..40e54ce3 --- /dev/null +++ b/plugins/ice/meson.build @@ -0,0 +1,28 @@ +dependencies = [ + dep_crypto_vala, + dep_dino, + dep_gdk_pixbuf, + dep_gee, + dep_glib, + dep_gmodule, + dep_gnutls, + dep_nice, + dep_qlite, + dep_xmpp_vala, +] +sources = files( + 'src/dtls_srtp.vala', + 'src/module.vala', + 'src/plugin.vala', + 'src/transport_parameters.vala', + 'src/util.vala', + 'src/register_plugin.vala', +) +c_args = [ + '-DG_LOG_DOMAIN="ice"', +] +vala_args = [ + '--vapidir', meson.current_source_dir() / 'vapi', +] +lib_ice = shared_library('ice', sources, name_prefix: '', c_args: c_args, vala_args: vala_args, dependencies: dependencies, install: true, install_dir: get_option('libdir') / 'dino/plugins') +dep_ice = declare_dependency(link_with: lib_ice, include_directories: include_directories('.')) diff --git a/plugins/meson.build b/plugins/meson.build new file mode 100644 index 00000000..196e3634 --- /dev/null +++ b/plugins/meson.build @@ -0,0 +1,6 @@ +subdir('http-files') +subdir('ice') +subdir('notification-sound') +subdir('omemo') +subdir('openpgp') +subdir('rtp') diff --git a/plugins/notification-sound/meson.build b/plugins/notification-sound/meson.build new file mode 100644 index 00000000..5a114d86 --- /dev/null +++ b/plugins/notification-sound/meson.build @@ -0,0 +1,19 @@ +dependencies = [ + dep_dino, + dep_gdk_pixbuf, + dep_gee, + dep_glib, + dep_gmodule, + dep_libcanberra, + dep_qlite, + dep_xmpp_vala, +] +sources = files( + 'src/plugin.vala', + 'src/register_plugin.vala', +) +vala_args = [ + '--vapidir', meson.current_source_dir() / 'vapi', +] +lib_notification_sound = shared_library('notification-sound', sources, name_prefix: '', vala_args: vala_args, dependencies: dependencies, install: true, install_dir: get_option('libdir') / 'dino/plugins') +dep_notification_sound = declare_dependency(link_with: lib_notification_sound, include_directories: include_directories('.')) diff --git a/plugins/omemo/CMakeLists.txt b/plugins/omemo/CMakeLists.txt index dc9a93b0..7ecaa0b8 100644 --- a/plugins/omemo/CMakeLists.txt +++ b/plugins/omemo/CMakeLists.txt @@ -12,6 +12,11 @@ find_packages(OMEMO_PACKAGES REQUIRED GTK4 ) +# 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) + set(RESOURCE_LIST contact_details_dialog.ui manage_key_dialog.ui @@ -52,6 +57,14 @@ SOURCES src/protocol/message_flag.vala src/protocol/stream_module.vala + src/signal/context.vala + src/signal/simple_iks.vala + src/signal/simple_ss.vala + src/signal/simple_pks.vala + src/signal/simple_spks.vala + src/signal/store.vala + src/signal/util.vala + src/ui/account_settings_entry.vala src/ui/bad_messages_populator.vala src/ui/call_encryption_entry.vala @@ -64,22 +77,52 @@ SOURCES src/ui/util.vala CUSTOM_VAPIS ${CMAKE_BINARY_DIR}/exports/crypto-vala.vapi - ${CMAKE_BINARY_DIR}/exports/signal-protocol.vapi ${CMAKE_BINARY_DIR}/exports/xmpp-vala.vapi ${CMAKE_BINARY_DIR}/exports/qlite.vapi ${CMAKE_BINARY_DIR}/exports/dino.vapi ${CMAKE_CURRENT_SOURCE_DIR}/vapi/libqrencode.vapi + ${CMAKE_CURRENT_SOURCE_DIR}/vapi/libsignal-protocol-c.vapi PACKAGES ${OMEMO_PACKAGES} GRESOURCES ${OMEMO_GRESOURCES_XML} +GENERATE_VAPI + omemo +GENERATE_HEADER + omemo ) -add_definitions(${VALA_CFLAGS} -DGETTEXT_PACKAGE=\"${GETTEXT_PACKAGE}\" -DLOCALE_INSTALL_DIR=\"${LOCALE_INSTALL_DIR}\" -DG_LOG_DOMAIN="OMEMO") -add_library(omemo SHARED ${OMEMO_VALA_C} ${OMEMO_GRESOURCES_TARGET}) +add_definitions(${VALA_CFLAGS} -DGETTEXT_PACKAGE=\"${GETTEXT_PACKAGE}\" -DLOCALE_INSTALL_DIR=\"${LOCALE_INSTALL_DIR}\" -DG_LOG_DOMAIN="OMEMO") +add_library(omemo SHARED ${OMEMO_VALA_C} ${OMEMO_GRESOURCES_TARGET} ${CMAKE_CURRENT_SOURCE_DIR}/src/signal/signal_helper.c) add_dependencies(omemo ${GETTEXT_PACKAGE}-translations) -target_link_libraries(omemo libdino signal-protocol-vala crypto-vala ${OMEMO_PACKAGES} libqrencode) +target_include_directories(omemo PUBLIC src) +target_link_libraries(omemo libdino crypto-vala gcrypt ${OMEMO_PACKAGES} libqrencode signal-protocol-c) set_target_properties(omemo PROPERTIES PREFIX "") set_target_properties(omemo PROPERTIES LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/plugins/) install(TARGETS omemo ${PLUGIN_INSTALL}) + +if(BUILD_TESTS) + vala_precompile(OMEMO_TEST_VALA_C + SOURCES + "tests/signal/common.vala" + "tests/signal/testcase.vala" + + "tests/signal/curve25519.vala" + "tests/signal/hkdf.vala" + "tests/signal/session_builder.vala" + CUSTOM_VAPIS + ${CMAKE_BINARY_DIR}/exports/omemo_internal.vapi + ${CMAKE_BINARY_DIR}/exports/qlite.vapi + ${CMAKE_BINARY_DIR}/exports/xmpp-vala.vapi + ${CMAKE_BINARY_DIR}/exports/dino.vapi + ${CMAKE_CURRENT_SOURCE_DIR}/vapi/libsignal-protocol-c.vapi + PACKAGES + ${OMEMO_PACKAGES} + ) + + set(CFLAGS ${VALA_CFLAGS}) + add_executable(omemo-test ${OMEMO_TEST_VALA_C}) + add_dependencies(omemo-test omemo) + target_link_libraries(omemo-test omemo ${OMEMO_PACKAGES}) +endif(BUILD_TESTS) diff --git a/plugins/omemo/data/gresource.xml b/plugins/omemo/data/gresource.xml new file mode 100644 index 00000000..616dcdc1 --- /dev/null +++ b/plugins/omemo/data/gresource.xml @@ -0,0 +1,7 @@ + + + + contact_details_dialog.ui + manage_key_dialog.ui + + diff --git a/plugins/omemo/meson.build b/plugins/omemo/meson.build new file mode 100644 index 00000000..57eec2ce --- /dev/null +++ b/plugins/omemo/meson.build @@ -0,0 +1,68 @@ +subdir('po') +dependencies = [ + dep_crypto_vala, + dep_dino, + dep_gee, + dep_glib, + dep_gmodule, + dep_gtk4, + dep_libgcrypt, + dep_libqrencode, + dep_libsignal_protocol_c, + dep_qlite, + dep_xmpp_vala, +] +sources = files( + 'src/dtls_srtp_verification_draft.vala', + 'src/file_transfer/file_decryptor.vala', + 'src/file_transfer/file_encryptor.vala', + 'src/jingle/jet_omemo.vala', + 'src/jingle/jingle_helper.vala', + 'src/logic/database.vala', + 'src/logic/decrypt.vala', + 'src/logic/encrypt.vala', + 'src/logic/manager.vala', + 'src/logic/pre_key_store.vala', + 'src/logic/session_store.vala', + 'src/logic/signed_pre_key_store.vala', + 'src/logic/trust_manager.vala', + 'src/plugin.vala', + 'src/protocol/bundle.vala', + 'src/protocol/message_flag.vala', + 'src/protocol/stream_module.vala', + 'src/register_plugin.vala', + 'src/signal/context.vala', + 'src/signal/signal_helper.c', + 'src/signal/simple_iks.vala', + 'src/signal/simple_pks.vala', + 'src/signal/simple_spks.vala', + 'src/signal/simple_ss.vala', + 'src/signal/store.vala', + 'src/signal/util.vala', + 'src/trust_level.vala', + 'src/ui/account_settings_entry.vala', + 'src/ui/bad_messages_populator.vala', + 'src/ui/call_encryption_entry.vala', + 'src/ui/contact_details_dialog.vala', + 'src/ui/contact_details_provider.vala', + 'src/ui/device_notification_populator.vala', + 'src/ui/encryption_list_entry.vala', + 'src/ui/manage_key_dialog.vala', + 'src/ui/own_notifications.vala', + 'src/ui/util.vala', +) +sources += gnome.compile_resources( + 'resources', + 'data/gresource.xml', + source_dir: 'data', +) +c_args = [ + '-DG_LOG_DOMAIN="OMEMO"', + '-DGETTEXT_PACKAGE="dino-omemo"', + '-DLOCALE_INSTALL_DIR="@0@"'.format(get_option('prefix') / get_option('localedir')), +] +vala_args = [ + '--vapidir', meson.current_source_dir() / 'vapi', +] +lib_omemo = shared_library('omemo', sources, name_prefix: '', c_args: c_args, vala_args: vala_args, include_directories: include_directories('src'), dependencies: dependencies, install: true, install_dir: get_option('libdir') / 'dino/plugins') +dep_omemo = declare_dependency(link_with: lib_omemo, include_directories: include_directories('.')) diff --git a/plugins/omemo/po/meson.build b/plugins/omemo/po/meson.build new file mode 100644 index 00000000..fa22f211 --- /dev/null +++ b/plugins/omemo/po/meson.build @@ -0,0 +1 @@ +i18n.gettext('dino-omemo') diff --git a/plugins/signal-protocol/src/context.vala b/plugins/omemo/src/signal/context.vala similarity index 100% rename from plugins/signal-protocol/src/context.vala rename to plugins/omemo/src/signal/context.vala diff --git a/plugins/signal-protocol/src/signal_helper.c b/plugins/omemo/src/signal/signal_helper.c similarity index 99% rename from plugins/signal-protocol/src/signal_helper.c rename to plugins/omemo/src/signal/signal_helper.c index 1a428c44..17682929 100644 --- a/plugins/signal-protocol/src/signal_helper.c +++ b/plugins/omemo/src/signal/signal_helper.c @@ -1,4 +1,4 @@ -#include +#include "signal_helper.h" #include diff --git a/plugins/signal-protocol/src/signal_helper.h b/plugins/omemo/src/signal/signal_helper.h similarity index 100% rename from plugins/signal-protocol/src/signal_helper.h rename to plugins/omemo/src/signal/signal_helper.h diff --git a/plugins/signal-protocol/src/simple_iks.vala b/plugins/omemo/src/signal/simple_iks.vala similarity index 100% rename from plugins/signal-protocol/src/simple_iks.vala rename to plugins/omemo/src/signal/simple_iks.vala diff --git a/plugins/signal-protocol/src/simple_pks.vala b/plugins/omemo/src/signal/simple_pks.vala similarity index 100% rename from plugins/signal-protocol/src/simple_pks.vala rename to plugins/omemo/src/signal/simple_pks.vala diff --git a/plugins/signal-protocol/src/simple_spks.vala b/plugins/omemo/src/signal/simple_spks.vala similarity index 100% rename from plugins/signal-protocol/src/simple_spks.vala rename to plugins/omemo/src/signal/simple_spks.vala diff --git a/plugins/signal-protocol/src/simple_ss.vala b/plugins/omemo/src/signal/simple_ss.vala similarity index 100% rename from plugins/signal-protocol/src/simple_ss.vala rename to plugins/omemo/src/signal/simple_ss.vala diff --git a/plugins/signal-protocol/src/store.vala b/plugins/omemo/src/signal/store.vala similarity index 100% rename from plugins/signal-protocol/src/store.vala rename to plugins/omemo/src/signal/store.vala diff --git a/plugins/signal-protocol/src/util.vala b/plugins/omemo/src/signal/util.vala similarity index 100% rename from plugins/signal-protocol/src/util.vala rename to plugins/omemo/src/signal/util.vala diff --git a/plugins/signal-protocol/tests/common.vala b/plugins/omemo/tests/signal/common.vala similarity index 100% rename from plugins/signal-protocol/tests/common.vala rename to plugins/omemo/tests/signal/common.vala diff --git a/plugins/signal-protocol/tests/curve25519.vala b/plugins/omemo/tests/signal/curve25519.vala similarity index 100% rename from plugins/signal-protocol/tests/curve25519.vala rename to plugins/omemo/tests/signal/curve25519.vala diff --git a/plugins/signal-protocol/tests/hkdf.vala b/plugins/omemo/tests/signal/hkdf.vala similarity index 100% rename from plugins/signal-protocol/tests/hkdf.vala rename to plugins/omemo/tests/signal/hkdf.vala diff --git a/plugins/signal-protocol/tests/session_builder.vala b/plugins/omemo/tests/signal/session_builder.vala similarity index 100% rename from plugins/signal-protocol/tests/session_builder.vala rename to plugins/omemo/tests/signal/session_builder.vala diff --git a/plugins/signal-protocol/tests/testcase.vala b/plugins/omemo/tests/signal/testcase.vala similarity index 100% rename from plugins/signal-protocol/tests/testcase.vala rename to plugins/omemo/tests/signal/testcase.vala diff --git a/plugins/omemo/vapi/libgcrypt.vapi b/plugins/omemo/vapi/libgcrypt.vapi new file mode 100644 index 00000000..e69de29b diff --git a/plugins/omemo/vapi/libsignal-protocol-c.vapi b/plugins/omemo/vapi/libsignal-protocol-c.vapi new file mode 100644 index 00000000..7c63d418 --- /dev/null +++ b/plugins/omemo/vapi/libsignal-protocol-c.vapi @@ -0,0 +1,657 @@ +namespace Signal { + + [CCode (cname = "int", cprefix = "SG_ERR_", cheader_filename = "signal/signal_protocol.h", has_type_id = false)] + public enum ErrorCode { + [CCode (cname = "SG_SUCCESS")] + SUCCESS, + NOMEM, + INVAL, + UNKNOWN, + DUPLICATE_MESSAGE, + INVALID_KEY, + INVALID_KEY_ID, + INVALID_MAC, + INVALID_MESSAGE, + INVALID_VERSION, + LEGACY_MESSAGE, + NO_SESSION, + STALE_KEY_EXCHANGE, + UNTRUSTED_IDENTITY, + VRF_SIG_VERIF_FAILED, + INVALID_PROTO_BUF, + FP_VERSION_MISMATCH, + FP_IDENT_MISMATCH; + } + + [CCode (cname = "SG_ERR_MINIMUM", cheader_filename = "signal/signal_protocol.h")] + public const int MIN_ERROR_CODE; + + [CCode (cname = "int", cprefix = "SG_LOG_", cheader_filename = "signal/signal_protocol.h", has_type_id = false)] + public enum LogLevel { + ERROR, + WARNING, + NOTICE, + INFO, + DEBUG + } + + [CCode (cname = "signal_throw_gerror_by_code_", cheader_filename = "signal/signal_protocol.h")] + private int throw_by_code(int code, string? message = null) throws GLib.Error { + if (code < 0 && code > MIN_ERROR_CODE) { + throw new GLib.Error(-1, code, "%s: %s", message ?? "Signal error", ((ErrorCode)code).to_string()); + } + return code; + } + + [CCode (cname = "int", cprefix = "SG_CIPHER_", cheader_filename = "signal/signal_protocol.h", has_type_id = false)] + public enum Cipher { + AES_CTR_NOPADDING, + AES_CBC_PKCS5, + AES_GCM_NOPADDING + } + + [Compact] + [CCode (cname = "signal_type_base", ref_function="signal_type_ref_vapi", unref_function="signal_type_unref_vapi", cheader_filename="signal/signal_protocol_types.h,signal/signal_helper.h")] + public class TypeBase { + } + + [Compact] + [CCode (cname = "signal_buffer", cheader_filename = "signal/signal_protocol_types.h", free_function="signal_buffer_free")] + public class Buffer { + [CCode (cname = "signal_buffer_alloc")] + public Buffer(size_t len); + [CCode (cname = "signal_buffer_create")] + public Buffer.from(uint8[] data); + + public Buffer copy(); + public Buffer append(uint8[] data); + public int compare(Buffer other); + + public uint8 get(int i) { return data[i]; } + public void set(int i, uint8 val) { data[i] = val; } + + public uint8[] data { get { int x = (int)len(); unowned uint8[] res = _data(); res.length = x; return res; } } + + [CCode (array_length = false, cname = "signal_buffer_data")] + private unowned uint8[] _data(); + private size_t len(); + } + + [Compact] + [CCode (cname = "signal_int_list", cheader_filename = "signal/signal_protocol_types.h", free_function="signal_int_list_free")] + public class IntList { + [CCode (cname = "signal_int_list_alloc")] + public IntList(); + [CCode (cname = "signal_int_list_push_back")] + public int add(int value); + public uint size { [CCode (cname = "signal_int_list_size")] get; } + [CCode (cname = "signal_int_list_at")] + public int get(uint index); + } + + [Compact] + [CCode (cname = "session_builder", cprefix = "session_builder_", free_function="session_builder_free", cheader_filename = "signal/session_builder.h")] + public class SessionBuilder { + [CCode (cname = "session_builder_process_pre_key_bundle")] + private int process_pre_key_bundle_(PreKeyBundle pre_key_bundle); + [CCode (cname = "session_builder_process_pre_key_bundle_")] + public void process_pre_key_bundle(PreKeyBundle pre_key_bundle) throws GLib.Error { + throw_by_code(process_pre_key_bundle_(pre_key_bundle)); + } + } + + [Compact] + [CCode (cname = "session_pre_key_bundle", cprefix = "session_pre_key_bundle_", cheader_filename = "signal/session_pre_key.h")] + public class PreKeyBundle : TypeBase { + public static int create(out PreKeyBundle bundle, uint32 registration_id, int device_id, uint32 pre_key_id, ECPublicKey? pre_key_public, + uint32 signed_pre_key_id, ECPublicKey? signed_pre_key_public, uint8[]? signed_pre_key_signature, ECPublicKey? identity_key); + public uint32 registration_id { get; } + public int device_id { get; } + public uint32 pre_key_id { get; } + public ECPublicKey pre_key { owned get; } + public uint32 signed_pre_key_id { get; } + public ECPublicKey signed_pre_key { owned get; } + public Buffer signed_pre_key_signature { owned get; } + public ECPublicKey identity_key { owned get; } + } + + [Compact] + [CCode (cname = "session_pre_key", cprefix = "session_pre_key_", cheader_filename = "signal/session_pre_key.h,signal/signal_helper.h")] + public class PreKeyRecord : TypeBase { + public static int create(out PreKeyRecord pre_key, uint32 id, ECKeyPair key_pair); + //public static int deserialize(out PreKeyRecord pre_key, uint8[] data, NativeContext global_context); + [CCode (instance_pos = 2)] + public int serialze(out Buffer buffer); + public uint32 id { get; } + public ECKeyPair key_pair { get; } + } + + [Compact] + [CCode (cname = "session_record", cprefix = "session_record_", cheader_filename = "signal/signal_protocol_types.h")] + public class SessionRecord : TypeBase { + public SessionState state { get; } + public Buffer user_record { get; } + } + + [Compact] + [CCode (cname = "session_state", cprefix = "session_state_", cheader_filename = "signal/session_state.h")] + public class SessionState : TypeBase { + //public static int create(out SessionState state, NativeContext context); + //public static int deserialize(out SessionState state, uint8[] data, NativeContext context); + //public static int copy(out SessionState state, SessionState other_state, NativeContext context); + [CCode (instance_pos = 2)] + public int serialze(out Buffer buffer); + + public uint32 session_version { get; set; } + public ECPublicKey local_identity_key { get; set; } + public ECPublicKey remote_identity_key { get; set; } + //public Ratchet.RootKey root_key { get; set; } + public uint32 previous_counter { get; set; } + public ECPublicKey sender_ratchet_key { get; } + public ECKeyPair sender_ratchet_key_pair { get; } + //public Ratchet.ChainKey sender_chain_key { get; set; } + public uint32 remote_registration_id { get; set; } + public uint32 local_registration_id { get; set; } + public int needs_refresh { get; set; } + public ECPublicKey alice_base_key { get; set; } + } + + [Compact] + [CCode (cname = "session_signed_pre_key", cprefix = "session_signed_pre_key_", cheader_filename = "signal/session_pre_key.h")] + public class SignedPreKeyRecord : TypeBase { + public static int create(out SignedPreKeyRecord pre_key, uint32 id, uint64 timestamp, ECKeyPair key_pair, uint8[] signature); + [CCode (instance_pos = 2)] + public int serialze(out Buffer buffer); + + public uint32 id { get; } + public uint64 timestamp { get; } + public ECKeyPair key_pair { get; } + public uint8[] signature { [CCode (cname = "session_signed_pre_key_get_signature_")] get { int x = (int)get_signature_len(); unowned uint8[] res = get_signature(); res.length = x; return res; } } + + [CCode (array_length = false, cname = "session_signed_pre_key_get_signature")] + private unowned uint8[] get_signature(); + private size_t get_signature_len(); + } + + /** + * Address of an Signal Protocol message recipient + */ + [Compact] + [CCode (cname = "signal_protocol_address", cprefix = "signal_protocol_address_", cheader_filename = "signal/signal_protocol.h,signal/signal_helper.h")] + public class Address { + public Address(string name, int32 device_id); + public int32 device_id { get; set; } + public string name { owned get; set; } + } + + /** + * A representation of a (group + sender + device) tuple + */ + [Compact] + [CCode (cname = "signal_protocol_sender_key_name")] + public class SenderKeyName { + [CCode (cname = "group_id", array_length_cname="group_id_len")] + private char* group_id_; + private size_t group_id_len; + public Address sender; + } + + [Compact] + [CCode (cname = "ec_public_key", cprefix = "ec_public_key_", cheader_filename = "signal/curve.h,signal/signal_helper.h")] + public class ECPublicKey : TypeBase { + [CCode (cname = "curve_generate_public_key")] + public static int generate(out ECPublicKey public_key, ECPrivateKey private_key); + [CCode (instance_pos = 1, cname = "ec_public_key_serialize")] + private int serialize_([CCode (pos = 0)] out Buffer buffer); + [CCode (cname = "ec_public_key_serialize_")] + public uint8[] serialize() { + Buffer buffer; + int code = serialize_(out buffer); + if (code < 0 && code > MIN_ERROR_CODE) { + // Can only throw for invalid arguments or out of memory. + GLib.assert_not_reached(); + } + return buffer.data; + } + public int compare(ECPublicKey other); + public int memcmp(ECPublicKey other); + } + + [Compact] + [CCode (cname = "ec_private_key", cprefix = "ec_private_key_", cheader_filename = "signal/curve.h,signal/signal_helper.h")] + public class ECPrivateKey : TypeBase { + [CCode (instance_pos = 1, cname = "ec_private_key_serialize")] + private int serialize_([CCode (pos = 0)] out Buffer buffer); + [CCode (cname = "ec_private_key_serialize_")] + public uint8[] serialize() throws GLib.Error { + Buffer buffer; + int code = serialize_(out buffer); + if (code < 0 && code > MIN_ERROR_CODE) { + // Can only throw for invalid arguments or out of memory. + GLib.assert_not_reached(); + } + return buffer.data; + } + public int compare(ECPublicKey other); + } + + [Compact] + [CCode (cname = "ec_key_pair", cprefix="ec_key_pair_", cheader_filename = "signal/curve.h,signal/signal_helper.h")] + public class ECKeyPair : TypeBase { + public static int create(out ECKeyPair key_pair, ECPublicKey public_key, ECPrivateKey private_key); + public ECPublicKey public { get; } + public ECPrivateKey private { get; } + } + + [CCode (cname = "ratchet_message_keys", cheader_filename = "signal/ratchet.h")] + public class MessageKeys { + } + + [Compact] + [CCode (cname = "ratchet_identity_key_pair", cprefix = "ratchet_identity_key_pair_", cheader_filename = "signal/ratchet.h,signal/signal_helper.h")] + public class IdentityKeyPair : TypeBase { + public static int create(out IdentityKeyPair key_pair, ECPublicKey public_key, ECPrivateKey private_key); + public int serialze(out Buffer buffer); + public ECPublicKey public { get; } + public ECPrivateKey private { get; } + } + + [Compact] + [CCode (cname = "ec_public_key_list")] + public class PublicKeyList {} + + /** + * The main entry point for Signal Protocol encrypt/decrypt operations. + * + * Once a session has been established with session_builder, + * this class can be used for all encrypt/decrypt operations within + * that session. + */ + [Compact] + [CCode (cname = "session_cipher", cprefix = "session_cipher_", cheader_filename = "signal/session_cipher.h", free_function = "session_cipher_free")] + public class SessionCipher { + public void* user_data { get; set; } + public DecryptionCallback decryption_callback { set; } + [CCode (cname = "session_cipher_encrypt")] + private int encrypt_(uint8[] padded_message, out CiphertextMessage encrypted_message); + [CCode (cname = "session_cipher_encrypt_")] + public CiphertextMessage encrypt(uint8[] padded_message) throws GLib.Error { + CiphertextMessage res; + throw_by_code(encrypt_(padded_message, out res)); + return res; + } + [CCode (cname = "session_cipher_decrypt_pre_key_signal_message")] + private int decrypt_pre_key_signal_message_(PreKeySignalMessage ciphertext, void* decrypt_context, out Buffer plaintext); + [CCode (cname = "session_cipher_decrypt_pre_key_signal_message_")] + public uint8[] decrypt_pre_key_signal_message(PreKeySignalMessage ciphertext, void* decrypt_context = null) throws GLib.Error { + Buffer res; + throw_by_code(decrypt_pre_key_signal_message_(ciphertext, decrypt_context, out res)); + return res.data; + } + [CCode (cname = "session_cipher_decrypt_signal_message")] + private int decrypt_signal_message_(SignalMessage ciphertext, void* decrypt_context, out Buffer plaintext); + [CCode (cname = "session_cipher_decrypt_signal_message_")] + public uint8[] decrypt_signal_message(SignalMessage ciphertext, void* decrypt_context = null) throws GLib.Error { + Buffer res; + throw_by_code(decrypt_signal_message_(ciphertext, decrypt_context, out res)); + return res.data; + } + public int get_remote_registration_id(out uint32 remote_id); + public int get_session_version(uint32 version); + + [CCode (has_target = false)] + public delegate int DecryptionCallback(SessionCipher cipher, Buffer plaintext, void* decrypt_context); + } + + [CCode (cname = "int", cheader_filename = "signal/protocol.h", has_type_id = false)] + public enum CiphertextType { + [CCode (cname = "CIPHERTEXT_SIGNAL_TYPE")] + SIGNAL, + [CCode (cname = "CIPHERTEXT_PREKEY_TYPE")] + PREKEY, + [CCode (cname = "CIPHERTEXT_SENDERKEY_TYPE")] + SENDERKEY, + [CCode (cname = "CIPHERTEXT_SENDERKEY_DISTRIBUTION_TYPE")] + SENDERKEY_DISTRIBUTION + } + + [Compact] + [CCode (cname = "ciphertext_message", cprefix = "ciphertext_message_", cheader_filename = "signal/protocol.h,signal/signal_helper.h")] + public abstract class CiphertextMessage : TypeBase { + public CiphertextType type { get; } + [CCode (cname = "ciphertext_message_get_serialized")] + private unowned Buffer get_serialized_(); + public uint8[] serialized { [CCode (cname = "ciphertext_message_get_serialized_")] get { + return get_serialized_().data; + }} + } + [Compact] + [CCode (cname = "signal_message", cprefix = "signal_message_", cheader_filename = "signal/protocol.h,signal/signal_helper.h")] + public class SignalMessage : CiphertextMessage { + public ECPublicKey sender_ratchet_key { get; } + public uint8 message_version { get; } + public uint32 counter { get; } + public Buffer body { get; } + //public int verify_mac(uint8 message_version, ECPublicKey sender_identity_key, ECPublicKey receiver_identity_key, uint8[] mac, NativeContext global_context); + public static int is_legacy(uint8[] data); + } + [Compact] + [CCode (cname = "pre_key_signal_message", cprefix = "pre_key_signal_message_", cheader_filename = "signal/protocol.h,signal/signal_helper.h")] + public class PreKeySignalMessage : CiphertextMessage { + public uint8 message_version { get; } + public ECPublicKey identity_key { get; } + public uint32 registration_id { get; } + public uint32 pre_key_id { get; } + public uint32 signed_pre_key_id { get; } + public ECPublicKey base_key { get; } + public SignalMessage signal_message { get; } + } + [Compact] + [CCode (cname = "sender_key_message", cprefix = "sender_key_message_", cheader_filename = "signal/protocol.h,signal/signal_helper.h")] + public class SenderKeyMessage : CiphertextMessage { + public uint32 key_id { get; } + public uint32 iteration { get; } + public Buffer ciphertext { get; } + } + [Compact] + [CCode (cname = "sender_key_distribution_message", cprefix = "sender_key_distribution_message_", cheader_filename = "signal/protocol.h,signal/signal_helper.h")] + public class SenderKeyDistributionMessage : CiphertextMessage { + public uint32 id { get; } + public uint32 iteration { get; } + public Buffer chain_key { get; } + public ECPublicKey signature_key { get; } + } + + [CCode (cname = "signal_vala_encrypt", cheader_filename = "signal/signal_helper.h")] + private static int aes_encrypt_(out Buffer output, int cipher, uint8[] key, uint8[] iv, uint8[] plaintext, void *user_data); + + [CCode (cname = "signal_vala_encrypt_")] + public uint8[] aes_encrypt(int cipher, uint8[] key, uint8[] iv, uint8[] plaintext) throws GLib.Error { + Buffer buf; + throw_by_code(aes_encrypt_(out buf, cipher, key, iv, plaintext, null)); + return buf.data; + } + + [CCode (cname = "signal_vala_decrypt", cheader_filename = "signal/signal_helper.h")] + private static int aes_decrypt_(out Buffer output, int cipher, uint8[] key, uint8[] iv, uint8[] ciphertext, void *user_data); + + [CCode (cname = "signal_vala_decrypt_")] + public uint8[] aes_decrypt(int cipher, uint8[] key, uint8[] iv, uint8[] ciphertext) throws GLib.Error { + Buffer buf; + throw_by_code(aes_decrypt_(out buf, cipher, key, iv, ciphertext, null)); + return buf.data; + } + + [Compact] + [CCode (cname = "signal_context", cprefix="signal_context_", free_function="signal_context_destroy", cheader_filename = "signal/signal_protocol.h")] + public class NativeContext { + public static int create(out NativeContext context, void* user_data); + public int set_crypto_provider(NativeCryptoProvider crypto_provider); + public int set_locking_functions(LockingFunc lock, LockingFunc unlock); + public int set_log_function(LogFunc log); + } + [CCode (has_target = false)] + public delegate void LockingFunc(void* user_data); + [CCode (has_target = false)] + public delegate void LogFunc(LogLevel level, string message, size_t len, void* user_data); + + [Compact] + [CCode (cname = "signal_crypto_provider", cheader_filename = "signal/signal_protocol.h")] + public struct NativeCryptoProvider { + public RandomFunc random_func; + public HmacSha256Init hmac_sha256_init_func; + public HmacSha256Update hmac_sha256_update_func; + public HmacSha256Final hmac_sha256_final_func; + public HmacSha256Cleanup hmac_sha256_cleanup_func; + public Sha512DigestInit sha512_digest_init_func; + public Sha512DigestUpdate sha512_digest_update_func; + public Sha512DigestFinal sha512_digest_final_func; + public Sha512DigestCleanup sha512_digest_cleanup_func; + public CryptFunc encrypt_func; + public CryptFunc decrypt_func; + public void* user_data; + } + [CCode (has_target = false)] + public delegate int RandomFunc(uint8[] data, void* user_data); + [CCode (has_target = false)] + public delegate int HmacSha256Init(out void* hmac_context, uint8[] key, void* user_data); + [CCode (has_target = false)] + public delegate int HmacSha256Update(void* hmac_context, uint8[] data, void* user_data); + [CCode (has_target = false)] + public delegate int HmacSha256Final(void* hmac_context, out Buffer buffer, void* user_data); + [CCode (has_target = false)] + public delegate int HmacSha256Cleanup(void* hmac_context, void* user_data); + [CCode (has_target = false)] + public delegate int Sha512DigestInit(out void* digest_context, void* user_data); + [CCode (has_target = false)] + public delegate int Sha512DigestUpdate(void* digest_context, uint8[] data, void* user_data); + [CCode (has_target = false)] + public delegate int Sha512DigestFinal(void* digest_context, out Buffer buffer, void* user_data); + [CCode (has_target = false)] + public delegate int Sha512DigestCleanup(void* digest_context, void* user_data); + [CCode (has_target = false)] + public delegate int CryptFunc(out Buffer output, Cipher cipher, uint8[] key, uint8[] iv, uint8[] content, void* user_data); + + [Compact] + [CCode (cname = "signal_protocol_session_store", cheader_filename = "signal/signal_protocol.h")] + public struct NativeSessionStore { + public LoadSessionFunc load_session_func; + public GetSubDeviceSessionsFunc get_sub_device_sessions_func; + public StoreSessionFunc store_session_func; + public ContainsSessionFunc contains_session_func; + public DeleteSessionFunc delete_session_func; + public DeleteAllSessionsFunc delete_all_sessions_func; + public DestroyFunc destroy_func; + public void* user_data; + } + [CCode (has_target = false)] + public delegate int LoadSessionFunc(out Buffer record, out Buffer user_record, Address address, void* user_data); + [CCode (has_target = false)] + public delegate int GetSubDeviceSessionsFunc(out IntList sessions, [CCode (array_length_type = "size_t")] char[] name, void* user_data); + [CCode (has_target = false)] + public delegate int StoreSessionFunc(Address address, [CCode (array_length_type = "size_t")] uint8[] record, [CCode (array_length_type = "size_t")] uint8[] user_record, void* user_data); + [CCode (has_target = false)] + public delegate int ContainsSessionFunc(Address address, void* user_data); + [CCode (has_target = false)] + public delegate int DeleteSessionFunc(Address address, void* user_data); + [CCode (has_target = false)] + public delegate int DeleteAllSessionsFunc([CCode (array_length_type = "size_t")] char[] name, void* user_data); + + [Compact] + [CCode (cname = "signal_protocol_identity_key_store", cheader_filename = "signal/signal_protocol.h")] + public struct NativeIdentityKeyStore { + GetIdentityKeyPairFunc get_identity_key_pair; + GetLocalRegistrationIdFunc get_local_registration_id; + SaveIdentityFunc save_identity; + IsTrustedIdentityFunc is_trusted_identity; + DestroyFunc destroy_func; + void* user_data; + } + [CCode (has_target = false)] + public delegate int GetIdentityKeyPairFunc(out Buffer public_data, out Buffer private_data, void* user_data); + [CCode (has_target = false)] + public delegate int GetLocalRegistrationIdFunc(void* user_data, out uint32 registration_id); + [CCode (has_target = false)] + public delegate int SaveIdentityFunc(Address address, [CCode (array_length_type = "size_t")] uint8[] key, void* user_data); + [CCode (has_target = false)] + public delegate int IsTrustedIdentityFunc(Address address, [CCode (array_length_type = "size_t")] uint8[] key, void* user_data); + + [Compact] + [CCode (cname = "signal_protocol_pre_key_store", cheader_filename = "signal/signal_protocol.h")] + public struct NativePreKeyStore { + LoadPreKeyFunc load_pre_key; + StorePreKeyFunc store_pre_key; + ContainsPreKeyFunc contains_pre_key; + RemovePreKeyFunc remove_pre_key; + DestroyFunc destroy_func; + void* user_data; + } + [CCode (has_target = false)] + public delegate int LoadPreKeyFunc(out Buffer record, uint32 pre_key_id, void* user_data); + [CCode (has_target = false)] + public delegate int StorePreKeyFunc(uint32 pre_key_id, [CCode (array_length_type = "size_t")] uint8[] record, void* user_data); + [CCode (has_target = false)] + public delegate int ContainsPreKeyFunc(uint32 pre_key_id, void* user_data); + [CCode (has_target = false)] + public delegate int RemovePreKeyFunc(uint32 pre_key_id, void* user_data); + + + [Compact] + [CCode (cname = "signal_protocol_signed_pre_key_store", cheader_filename = "signal/signal_protocol.h")] + public struct NativeSignedPreKeyStore { + LoadPreKeyFunc load_signed_pre_key; + StorePreKeyFunc store_signed_pre_key; + ContainsPreKeyFunc contains_signed_pre_key; + RemovePreKeyFunc remove_signed_pre_key; + DestroyFunc destroy_func; + void* user_data; + } + + + [Compact] + [CCode (cname = "signal_protocol_sender_key_store")] + public struct NativeSenderKeyStore { + StoreSenderKeyFunc store_sender_key; + LoadSenderKeyFunc load_sender_key; + DestroyFunc destroy_func; + void* user_data; + } + [CCode (has_target = false)] + public delegate int StoreSenderKeyFunc(SenderKeyName sender_key_name, [CCode (array_length_type = "size_t")] uint8[] record, [CCode (array_length_type = "size_t")] uint8[] user_record, void* user_data); + [CCode (has_target = false)] + public delegate int LoadSenderKeyFunc(out Buffer record, out Buffer user_record, SenderKeyName sender_key_name, void* user_data); + + [CCode (has_target = false)] + public delegate void DestroyFunc(void* user_data); + + [Compact] + [CCode (cname = "signal_protocol_store_context", cprefix = "signal_protocol_store_context_", free_function="signal_protocol_store_context_destroy", cheader_filename = "signal/signal_protocol.h")] + public class NativeStoreContext { + public static int create(out NativeStoreContext context, NativeContext global_context); + public int set_session_store(NativeSessionStore store); + public int set_pre_key_store(NativePreKeyStore store); + public int set_signed_pre_key_store(NativeSignedPreKeyStore store); + public int set_identity_key_store(NativeIdentityKeyStore store); + public int set_sender_key_store(NativeSenderKeyStore store); + } + + + [CCode (cheader_filename = "signal/signal_protocol.h")] + namespace Protocol { + + /** + * Interface to the pre-key store. + * These functions will use the callbacks in the provided + * signal_protocol_store_context instance and operate in terms of higher level + * library data structures. + */ + [CCode (cprefix = "signal_protocol_pre_key_")] + namespace PreKey { + public int load_key(NativeStoreContext context, out PreKeyRecord pre_key, uint32 pre_key_id); + public int store_key(NativeStoreContext context, PreKeyRecord pre_key); + public int contains_key(NativeStoreContext context, uint32 pre_key_id); + public int remove_key(NativeStoreContext context, uint32 pre_key_id); + } + + [CCode (cprefix = "signal_protocol_signed_pre_key_")] + namespace SignedPreKey { + public int load_key(NativeStoreContext context, out SignedPreKeyRecord pre_key, uint32 pre_key_id); + public int store_key(NativeStoreContext context, SignedPreKeyRecord pre_key); + public int contains_key(NativeStoreContext context, uint32 pre_key_id); + public int remove_key(NativeStoreContext context, uint32 pre_key_id); + } + + /** + * Interface to the session store. + * These functions will use the callbacks in the provided + * signal_protocol_store_context instance and operate in terms of higher level + * library data structures. + */ + [CCode (cprefix = "signal_protocol_session_")] + namespace Session { + public int load_session(NativeStoreContext context, out SessionRecord record, Address address); + public int get_sub_device_sessions(NativeStoreContext context, out IntList sessions, char[] name); + public int store_session(NativeStoreContext context, Address address, SessionRecord record); + public int contains_session(NativeStoreContext context, Address address); + public int delete_session(NativeStoreContext context, Address address); + public int delete_all_sessions(NativeStoreContext context, char[] name); + } + + namespace Identity { + public int get_key_pair(NativeStoreContext store_context, out IdentityKeyPair key_pair); + public int get_local_registration_id(NativeStoreContext store_context, out uint32 registration_id); + public int save_identity(NativeStoreContext store_context, Address address, ECPublicKey identity_key); + public int is_trusted_identity(NativeStoreContext store_context, Address address, ECPublicKey identity_key); + } + + [CCode (cheader_filename = "signal/key_helper.h", cprefix = "signal_protocol_key_helper_")] + namespace KeyHelper { + [Compact] + [CCode (cname = "signal_protocol_key_helper_pre_key_list_node", cprefix = "signal_protocol_key_helper_key_list_", free_function="signal_protocol_key_helper_key_list_free")] + public class PreKeyListNode { + public PreKeyRecord element(); + public PreKeyListNode next(); + } + + public int generate_identity_key_pair(out IdentityKeyPair key_pair, NativeContext global_context); + public int generate_registration_id(out int32 registration_id, int extended_range, NativeContext global_context); + public int get_random_sequence(out int value, int max, NativeContext global_context); + public int generate_pre_keys(out PreKeyListNode head, uint start, uint count, NativeContext global_context); + public int generate_last_resort_pre_key(out PreKeyRecord pre_key, NativeContext global_context); + public int generate_signed_pre_key(out SignedPreKeyRecord signed_pre_key, IdentityKeyPair identity_key_pair, uint32 signed_pre_key_id, uint64 timestamp, NativeContext global_context); + public int generate_sender_signing_key(out ECKeyPair key_pair, NativeContext global_context); + public int generate_sender_key(out Buffer key_buffer, NativeContext global_context); + public int generate_sender_key_id(out int32 key_id, NativeContext global_context); + } + } + + [CCode (cheader_filename = "signal/curve.h")] + namespace Curve { + [CCode (cname = "curve_calculate_agreement")] + public int calculate_agreement([CCode (array_length = false)] out uint8[] shared_key_data, ECPublicKey public_key, ECPrivateKey private_key); + [CCode (cname = "curve_calculate_signature")] + public int calculate_signature(NativeContext context, out Buffer signature, ECPrivateKey signing_key, uint8[] message); + [CCode (cname = "curve_verify_signature")] + public int verify_signature(ECPublicKey signing_key, uint8[] message, uint8[] signature); + } + + [CCode (cname = "session_builder_create", cheader_filename = "signal/session_builder.h")] + public static int session_builder_create(out SessionBuilder builder, NativeStoreContext store, Address remote_address, NativeContext global_context); + [CCode (cname = "session_cipher_create", cheader_filename = "signal/session_cipher.h")] + public static int session_cipher_create(out SessionCipher cipher, NativeStoreContext store, Address remote_address, NativeContext global_context); + [CCode (cname = "pre_key_signal_message_deserialize", cheader_filename = "signal/protocol.h")] + public static int pre_key_signal_message_deserialize(out PreKeySignalMessage message, uint8[] data, NativeContext global_context); + [CCode (cname = "pre_key_signal_message_copy", cheader_filename = "signal/protocol.h")] + public static int pre_key_signal_message_copy(out PreKeySignalMessage message, PreKeySignalMessage other_message, NativeContext global_context); + [CCode (cname = "signal_message_create", cheader_filename = "signal/protocol.h")] + public static int signal_message_create(out SignalMessage message, uint8 message_version, uint8[] mac_key, ECPublicKey sender_ratchet_key, uint32 counter, uint32 previous_counter, uint8[] ciphertext, ECPublicKey sender_identity_key, ECPublicKey receiver_identity_key, NativeContext global_context); + [CCode (cname = "signal_message_deserialize", cheader_filename = "signal/protocol.h")] + public static int signal_message_deserialize(out SignalMessage message, uint8[] data, NativeContext global_context); + [CCode (cname = "signal_message_copy", cheader_filename = "signal/protocol.h")] + public static int signal_message_copy(out SignalMessage message, SignalMessage other_message, NativeContext global_context); + [CCode (cname = "curve_generate_key_pair", cheader_filename = "signal/curve.h")] + public static int curve_generate_key_pair(NativeContext context, out ECKeyPair key_pair); + [CCode (cname = "curve_decode_private_point", cheader_filename = "signal/curve.h")] + public static int curve_decode_private_point(out ECPrivateKey public_key, uint8[] key, NativeContext global_context); + [CCode (cname = "curve_decode_point", cheader_filename = "signal/curve.h")] + public static int curve_decode_point(out ECPublicKey public_key, uint8[] key, NativeContext global_context); + [CCode (cname = "curve_generate_private_key", cheader_filename = "signal/curve.h")] + public static int curve_generate_private_key(NativeContext context, out ECPrivateKey private_key); + [CCode (cname = "ratchet_identity_key_pair_deserialize", cheader_filename = "signal/ratchet.h")] + public static int ratchet_identity_key_pair_deserialize(out IdentityKeyPair key_pair, uint8[] data, NativeContext global_context); + [CCode (cname = "session_signed_pre_key_deserialize", cheader_filename = "signal/signed_pre_key.h")] + public static int session_signed_pre_key_deserialize(out SignedPreKeyRecord pre_key, uint8[] data, NativeContext global_context); + + [Compact] + [CCode (cname = "hkdf_context", cprefix = "hkdf_", free_function = "hkdf_destroy", cheader_filename = "signal/hkdf.h")] + public class NativeHkdfContext { + public static int create(out NativeHkdfContext context, int message_version, NativeContext global_context); + public int compare(NativeHkdfContext other); + public ssize_t derive_secrets([CCode (array_length = false)] out uint8[] output, uint8[] input_key_material, uint8[] salt, uint8[] info, size_t output_len); + } + + [CCode (cname = "setup_signal_vala_crypto_provider", cheader_filename = "signal/signal_helper.h")] + public static void setup_crypto_provider(NativeContext context); + [CCode (cname = "signal_vala_randomize", cheader_filename = "signal/signal_helper.h")] + public static int native_random(uint8[] data); +} diff --git a/plugins/openpgp/CMakeLists.txt b/plugins/openpgp/CMakeLists.txt index 649a55ad..6ed7bf53 100644 --- a/plugins/openpgp/CMakeLists.txt +++ b/plugins/openpgp/CMakeLists.txt @@ -1,3 +1,5 @@ +find_package(GPGME REQUIRED) + set(GETTEXT_PACKAGE "dino-openpgp") find_package(Gettext) include(${GETTEXT_USE_FILE}) @@ -28,6 +30,8 @@ compile_gresources( vala_precompile(OPENPGP_VALA_C SOURCES + src/gpgme_helper.vala + src/file_transfer/file_decryptor.vala src/file_transfer/file_encryptor.vala @@ -42,7 +46,8 @@ SOURCES src/stream_module.vala src/util.vala CUSTOM_VAPIS - ${CMAKE_BINARY_DIR}/exports/gpgme.vapi + ${CMAKE_CURRENT_SOURCE_DIR}/vapi/gpgme.vapi + ${CMAKE_CURRENT_SOURCE_DIR}/vapi/gpg-error.vapi ${CMAKE_BINARY_DIR}/exports/xmpp-vala.vapi ${CMAKE_BINARY_DIR}/exports/qlite.vapi ${CMAKE_BINARY_DIR}/exports/dino.vapi @@ -53,9 +58,10 @@ GRESOURCES ) add_definitions(${VALA_CFLAGS} -DG_LOG_DOMAIN="OpenPGP" -DGETTEXT_PACKAGE=\"${GETTEXT_PACKAGE}\" -DLOCALE_INSTALL_DIR=\"${LOCALE_INSTALL_DIR}\") -add_library(openpgp SHARED ${OPENPGP_VALA_C} ${OPENPGP_GRESOURCES_TARGET}) +add_library(openpgp SHARED ${OPENPGP_VALA_C} ${OPENPGP_GRESOURCES_TARGET} src/gpgme_fix.c) add_dependencies(openpgp ${GETTEXT_PACKAGE}-translations) -target_link_libraries(openpgp libdino gpgme-vala ${OPENPGP_PACKAGES}) +target_include_directories(openpgp PRIVATE src) +target_link_libraries(openpgp libdino gpgme ${OPENPGP_PACKAGES}) set_target_properties(openpgp PROPERTIES PREFIX "") set_target_properties(openpgp PROPERTIES LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/plugins/) diff --git a/plugins/openpgp/data/gresource.xml b/plugins/openpgp/data/gresource.xml new file mode 100644 index 00000000..fbe2e8e9 --- /dev/null +++ b/plugins/openpgp/data/gresource.xml @@ -0,0 +1,6 @@ + + + + account_settings_item.ui + + diff --git a/plugins/openpgp/meson.build b/plugins/openpgp/meson.build new file mode 100644 index 00000000..806494f2 --- /dev/null +++ b/plugins/openpgp/meson.build @@ -0,0 +1,43 @@ +subdir('po') +dependencies = [ + dep_dino, + dep_gee, + dep_glib, + dep_gmodule, + dep_gpgme, + dep_gtk4, + dep_qlite, + dep_xmpp_vala, +] +sources = files( + 'src/account_settings_entry.vala', + 'src/contact_details_provider.vala', + 'src/database.vala', + 'src/encryption_list_entry.vala', + 'src/file_transfer/file_decryptor.vala', + 'src/file_transfer/file_encryptor.vala', + 'src/gpgme_fix.c', + 'src/gpgme_helper.vala', + 'src/manager.vala', + 'src/plugin.vala', + 'src/register_plugin.vala', + 'src/stream_flag.vala', + 'src/stream_module.vala', + 'src/util.vala', + 'vapi/gpg-error.vapi', +) +sources += gnome.compile_resources( + 'resources', + 'data/gresource.xml', + source_dir: 'data', +) +c_args = [ + '-DG_LOG_DOMAIN="OpenPGP"', + '-DGETTEXT_PACKAGE="dino-openpgp"', + '-DLOCALE_INSTALL_DIR="@0@"'.format(get_option('prefix') / get_option('localedir')), +] +vala_args = [ + '--vapidir', meson.current_source_dir() / 'vapi', +] +lib_openpgp = shared_library('openpgp', sources, name_prefix: '', c_args: c_args, vala_args: vala_args, include_directories: include_directories('src'), dependencies: dependencies, install: true, install_dir: get_option('libdir') / 'dino/plugins') +dep_openpgp = declare_dependency(link_with: lib_openpgp, include_directories: include_directories('.')) diff --git a/plugins/openpgp/po/meson.build b/plugins/openpgp/po/meson.build new file mode 100644 index 00000000..ac755b55 --- /dev/null +++ b/plugins/openpgp/po/meson.build @@ -0,0 +1 @@ +i18n.gettext('dino-openpgp') diff --git a/plugins/openpgp/src/account_settings_entry.vala b/plugins/openpgp/src/account_settings_entry.vala index d2e5ac23..7c99942f 100644 --- a/plugins/openpgp/src/account_settings_entry.vala +++ b/plugins/openpgp/src/account_settings_entry.vala @@ -116,8 +116,10 @@ public class AccountSettingsEntry : Plugins.AccountSettingsEntry { SourceFunc callback = fetch_keys.callback; new Thread (null, () => { // Querying GnuPG might take some time try { - keys = GPGHelper.get_keylist(null, true); - } catch (Error e) { } + keys = GPGHelper.get_keylist(null, true); + } catch (Error e) { + warning(e.message); + } Idle.add((owned)callback); return null; }); diff --git a/plugins/gpgme-vala/src/gpgme_fix.c b/plugins/openpgp/src/gpgme_fix.c similarity index 82% rename from plugins/gpgme-vala/src/gpgme_fix.c rename to plugins/openpgp/src/gpgme_fix.c index 2bc139e9..bf457a6c 100644 --- a/plugins/gpgme-vala/src/gpgme_fix.c +++ b/plugins/openpgp/src/gpgme_fix.c @@ -1,6 +1,6 @@ #include -static GRecMutex gpgme_global_mutex = {0}; +GRecMutex gpgme_global_mutex = {0}; gpgme_key_t gpgme_key_ref_vapi (gpgme_key_t key) { gpgme_key_ref(key); @@ -9,4 +9,4 @@ gpgme_key_t gpgme_key_ref_vapi (gpgme_key_t key) { gpgme_key_t gpgme_key_unref_vapi (gpgme_key_t key) { gpgme_key_unref(key); return key; -} \ No newline at end of file +} diff --git a/plugins/gpgme-vala/src/gpgme_fix.h b/plugins/openpgp/src/gpgme_fix.h similarity index 80% rename from plugins/gpgme-vala/src/gpgme_fix.h rename to plugins/openpgp/src/gpgme_fix.h index 3daa7db0..714614fc 100644 --- a/plugins/gpgme-vala/src/gpgme_fix.h +++ b/plugins/openpgp/src/gpgme_fix.h @@ -4,9 +4,9 @@ #include #include -static GRecMutex gpgme_global_mutex; +extern GRecMutex gpgme_global_mutex; gpgme_key_t gpgme_key_ref_vapi (gpgme_key_t key); gpgme_key_t gpgme_key_unref_vapi (gpgme_key_t key); -#endif \ No newline at end of file +#endif diff --git a/plugins/gpgme-vala/src/gpgme_helper.vala b/plugins/openpgp/src/gpgme_helper.vala similarity index 99% rename from plugins/gpgme-vala/src/gpgme_helper.vala rename to plugins/openpgp/src/gpgme_helper.vala index ee4d0095..956ea1c8 100644 --- a/plugins/gpgme-vala/src/gpgme_helper.vala +++ b/plugins/openpgp/src/gpgme_helper.vala @@ -117,6 +117,7 @@ public static Gee.List get_keylist(string? pattern = null, bool secret_only } catch (Error e) { if (e.code != GPGError.ErrorCode.EOF) throw e; } + context.op_keylist_end(); return keys; } finally { global_mutex.unlock(); diff --git a/plugins/gpgme-vala/vapi/gpg-error.vapi b/plugins/openpgp/vapi/gpg-error.vapi similarity index 97% rename from plugins/gpgme-vala/vapi/gpg-error.vapi rename to plugins/openpgp/vapi/gpg-error.vapi index 2c915c8a..3ad6c580 100644 --- a/plugins/gpgme-vala/vapi/gpg-error.vapi +++ b/plugins/openpgp/vapi/gpg-error.vapi @@ -441,11 +441,5 @@ namespace GPGError { public Error.from_errno (ErrorSource source, int err); public ErrorCode code { [CCode (cname = "gpg_err_code")] get; } public ErrorSource source { [CCode (cname = "gpg_err_source")] get; } - - [CCode (cname = "gpg_strerror")] - public unowned string to_string (); - - [CCode (cname = "gpg_strsource")] - public unowned string source_to_string (); } -} \ No newline at end of file +} diff --git a/plugins/gpgme-vala/vapi/gpgme.vapi b/plugins/openpgp/vapi/gpgme.vapi similarity index 78% rename from plugins/gpgme-vala/vapi/gpgme.vapi rename to plugins/openpgp/vapi/gpgme.vapi index 8723bd81..2fc27c65 100644 --- a/plugins/gpgme-vala/vapi/gpgme.vapi +++ b/plugins/openpgp/vapi/gpgme.vapi @@ -1,6 +1,7 @@ /* libgpgme.vapi * * Copyright (C) 2009 Sebastian Reichel + * Copyright (C) 2022 Itay Grudev * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -36,6 +37,100 @@ namespace GPG { string? home_dir; } + [Compact] + [CCode (cname = "struct _gpgme_key", ref_function = "gpgme_key_ref_vapi", unref_function = "gpgme_key_unref_vapi", free_function = "gpgme_key_release")] + public class Key { + public bool revoked; + public bool expired; + public bool disabled; + public bool invalid; + public bool can_encrypt; + public bool can_sign; + public bool can_certify; + public bool secret; + public bool can_authenticate; + public bool is_qualified; + public Protocol protocol; + public string issuer_serial; + public string issuer_name; + public string chain_id; + public Validity owner_trust; + [CCode(array_null_terminated = true)] + public SubKey[] subkeys; + [CCode(array_null_terminated = true)] + public UserID[] uids; + public KeylistMode keylist_mode; + public string fpr { get { return subkeys[0].fpr; } } + } + + [CCode (cname = "struct _gpgme_sig_notation")] + public struct SigNotation { + SigNotation* next; + string? name; + string value; + int name_len; + int value_len; + SigNotationFlags flags; + bool human_readable; + bool critical; + } + + [CCode (cname = "struct _gpgme_subkey")] + public struct SubKey { + SubKey* next; + bool revoked; + bool expired; + bool disabled; + bool invalid; + bool can_encrypt; + bool can_sign; + bool can_certify; + bool secret; + bool can_authenticate; + bool is_qualified; + bool is_cardkey; + PublicKeyAlgorithm algo; + uint length; + string keyid; + string fpr; + long timestamp; + long expires; + string? card_number; + } + + [CCode (cname = "struct _gpgme_key_sig")] + public struct KeySig { + KeySig* next; + bool revoked; + bool expired; + bool invalid; + bool exportable; + PublicKeyAlgorithm algo; + string keyid; + long timestamp; + long expires; + GPGError.Error status; + string uid; + string name; + string email; + string comment; + uint sig_class; + SigNotation notations; + } + + [CCode (cname = "struct _gpgme_user_id")] + public struct UserID { + UserID* next; + bool revoked; + bool invalid; + Validity validity; + string uid; + string name; + string email; + string comment; + KeySig signatures; + } + [CCode (cname = "struct _gpgme_op_verify_result")] public struct VerifyResult { Signature* signatures; @@ -92,7 +187,7 @@ namespace GPG { GPGError.Error validity_reason; PublicKeyAlgorithm pubkey_algo; HashAlgorithm hash_algo; - string? pka_adress; + string? pka_address; } public enum PKAStatus { @@ -128,6 +223,16 @@ namespace GPG { URL0 } + [CCode (cname = "gpgme_pubkey_algo_t", cprefix = "GPGME_PK_")] + public enum PublicKeyAlgorithm { + RSA, + RSA_E, + RSA_S, + ELG_E, + DSA, + ELG + } + [CCode (cname = "gpgme_hash_algo_t", cprefix = "GPGME_MD_")] public enum HashAlgorithm { NONE, @@ -141,9 +246,45 @@ namespace GPG { SHA384, SHA512, MD4, - MD_CRC32, - MD_CRC32_RFC1510, - MD_CRC24_RFC2440 + CRC32, + CRC32_RFC1510, + CRC24_RFC2440 + } + + [CCode (cname = "gpgme_sig_mode_t", cprefix = "GPGME_SIG_MODE_")] + public enum SigMode { + NORMAL, + DETACH, + CLEAR + } + + [CCode (cname = "gpgme_validity_t", cprefix = "GPGME_VALIDITY_")] + public enum Validity { + UNKNOWN, + UNDEFINED, + NEVER, + MARGINAL, + FULL, + ULTIMATE + } + + [CCode (cname = "gpgme_protocol_t", cprefix = "GPGME_PROTOCOL_")] + public enum Protocol { + OpenPGP, + CMS, + GPGCONF, + ASSUAN, + UNKNOWN + } + + [CCode (cname = "gpgme_keylist_mode_t", cprefix = "GPGME_KEYLIST_MODE_")] + public enum KeylistMode { + LOCAL, + EXTERN, + SIGS, + SIG_NOTATIONS, + EPHEMERAL, + VALIDATE } [CCode (cname = "gpgme_export_mode_t", cprefix = "GPGME_EXPORT_MODE_")] @@ -157,6 +298,18 @@ namespace GPG { WITH_HELP } + [CCode (cname = "gpgme_sig_notation_flags_t", cprefix = "GPGME_SIG_NOTATION_")] + public enum SigNotationFlags { + HUMAN_READABLE, + CRITICAL + } + + [CCode (cname = "gpgme_encrypt_flags_t", cprefix = "GPGME_ENCRYPT_")] + public enum EncryptFlags { + ALWAYS_TRUST, + NO_ENCRYPT_TO + } + [CCode (cname = "gpgme_status_code_t", cprefix = "GPGME_STATUS_")] public enum StatusCode { EOF, @@ -244,21 +397,6 @@ namespace GPG { PLAINTEXT } - [Flags] - [CCode (cname="unsigned int")] - public enum ImportStatusFlags { - [CCode (cname = "GPGME_IMPORT_NEW")] - NEW, - [CCode (cname = "GPGME_IMPORT_UID")] - UID, - [CCode (cname = "GPGME_IMPORT_SIG")] - SIG, - [CCode (cname = "GPGME_IMPORT_SUBKEY")] - SUBKEY, - [CCode (cname = "GPGME_IMPORT_SECRET")] - SECRET - } - [Compact] [CCode (cname = "struct gpgme_context", free_function = "gpgme_release", cprefix = "gpgme_")] public class Context { @@ -305,11 +443,11 @@ namespace GPG { public Key* signers_enum(int n); public void sig_notation_clear(); - + public GPGError.Error sig_notation_add(string name, string val, SigNotationFlags flags); public SigNotation* sig_notation_get(); - + [CCode (cname = "gpgme_get_key")] private GPGError.Error get_key_(string fpr, out Key key, bool secret); @@ -319,7 +457,7 @@ namespace GPG { throw_if_error(get_key_(fpr, out key, secret)); return key; } - + public Context* wait(out GPGError.Error status, bool hang); public SignResult* op_sign_result(); @@ -405,10 +543,24 @@ namespace GPG { public KeylistResult op_keylist_result(); } + [Flags] + [CCode (cname="unsigned int")] + public enum ImportStatusFlags { + [CCode (cname = "GPGME_IMPORT_NEW")] + NEW, + [CCode (cname = "GPGME_IMPORT_UID")] + UID, + [CCode (cname = "GPGME_IMPORT_SIG")] + SIG, + [CCode (cname = "GPGME_IMPORT_SUBKEY")] + SUBKEY, + [CCode (cname = "GPGME_IMPORT_SECRET")] + SECRET + } + [Compact] [CCode (cname = "struct _gpgme_import_status")] public class ImportStatus { - public ImportStatus? next; public string fpr; public GPGError.Error result; @@ -443,7 +595,7 @@ namespace GPG { [Compact] [CCode (cname = "struct gpgme_data", free_function = "gpgme_data_release", cprefix = "gpgme_data_")] public class Data { - + public static GPGError.Error new(out Data d); public static Data create() throws GLib.Error { @@ -452,7 +604,6 @@ namespace GPG { return data; } - [CCode (cname = "gpgme_data_new_from_mem")] public static GPGError.Error new_from_memory(out Data d, char[] buffer, bool copy); @@ -482,7 +633,7 @@ namespace GPG { public GPGError.Error set_file_name(string file_name); - public DataEncoding* get_encoding(); + public DataEncoding *get_encoding(); public GPGError.Error set_encoding(DataEncoding enc); } @@ -499,11 +650,14 @@ namespace GPG { [CCode (cname = "gpgme_passphrase_cb_t", has_target = false)] public delegate GPGError.Error passphrase_callback(void* hook, string uid_hint, string passphrase_info, bool prev_was_bad, int fd); + [CCode (cname = "gpgme_check_version")] + public unowned string check_version(string? required_version = null); + [CCode (cname = "gpgme_engine_check_version")] public GPGError.Error engine_check_version(Protocol proto); - [CCode (cname = "gpgme_get_engine_information")] - public GPGError.Error get_engine_information(out EngineInfo engine_info); + [CCode (cname = "gpgme_get_engine_info")] + public GPGError.Error get_engine_info(out EngineInfo? engine_info); [CCode (cname = "gpgme_strerror_r")] public int strerror_r(GPGError.Error err, uint8[] buf); diff --git a/plugins/rtp/meson.build b/plugins/rtp/meson.build new file mode 100644 index 00000000..06821c91 --- /dev/null +++ b/plugins/rtp/meson.build @@ -0,0 +1,64 @@ +dependencies = [ + dep_crypto_vala, + dep_dino, + dep_gee, + dep_glib, + dep_gmodule, + dep_gnutls, + dep_gstreamer, + dep_gstreamer_app, + dep_gstreamer_audio, + dep_gstreamer_rtp, + dep_gstreamer_video, + dep_gtk4, + dep_m, + dep_qlite, + dep_xmpp_vala, +] +sources = files( + 'src/codec_util.vala', + 'src/device.vala', + 'src/gst_fixes.c', + 'src/module.vala', + 'src/plugin.vala', + 'src/register_plugin.vala', + 'src/stream.vala', + 'src/video_widget.vala', +) +c_args = [ + '-DG_LOG_DOMAIN="rtp"', +] +vala_args = [ + '--vapidir', meson.current_source_dir() / 'vapi', +] +if dep_webrtc_audio_processing.found() + dependencies += [dep_webrtc_audio_processing] + sources += files( + 'src/voice_processor.vala', + 'src/voice_processor_native.cpp', + ) + vala_args += ['-D', 'WITH_VOICE_PROCESSOR'] +endif +if dep_gstreamer_rtp.version() == 'unknown' or dep_gstreamer_rtp.version().version_compare('>=1.16') + vala_args += ['-D', 'GST_1_16'] +endif +if dep_gstreamer_rtp.version() == 'unknown' or dep_gstreamer_rtp.version().version_compare('>=1.18') + vala_args += ['-D', 'GST_1_18'] +endif +if dep_gstreamer_rtp.version() == 'unknown' or dep_gstreamer_rtp.version().version_compare('>=1.20') + vala_args += ['-D', 'GST_1_20'] +endif +if get_option('plugin-rtp-h264').allowed() + vala_args += ['-D', 'ENABLE_H264'] +endif +if get_option('plugin-rtp-msdk').allowed() + vala_args += ['-D', 'ENABLE_MSDK'] +endif +if get_option('plugin-rtp-vaapi').allowed() + vala_args += ['-D', 'ENABLE_VAAPI'] +endif +if get_option('plugin-rtp-vp9').allowed() + vala_args += ['-D', 'ENABLE_VP9'] +endif +lib_rtp = shared_library('rtp', sources, name_prefix: '', c_args: c_args, vala_args: vala_args, include_directories: include_directories('src'), dependencies: dependencies, install: true, install_dir: get_option('libdir') / 'dino/plugins') +dep_rtp = declare_dependency(link_with: lib_rtp, include_directories: include_directories('.')) diff --git a/plugins/rtp/src/video_widget.vala b/plugins/rtp/src/video_widget.vala index f69a2ba7..05cc5a6c 100644 --- a/plugins/rtp/src/video_widget.vala +++ b/plugins/rtp/src/video_widget.vala @@ -16,7 +16,7 @@ public class Dino.Plugins.Rtp.Paintable : Gdk.Paintable, Object { public override Gdk.Paintable get_current_image() { if (image != null) return image; - return Gdk.Paintable.new_empty(0, 0); + return Gdk.Paintable.empty(0, 0); } public override int get_intrinsic_width() { diff --git a/plugins/rtp/vapi/webrtc-audio-processing.vapi b/plugins/rtp/vapi/webrtc-audio-processing.vapi new file mode 100644 index 00000000..e69de29b diff --git a/plugins/signal-protocol/CMakeLists.txt b/plugins/signal-protocol/CMakeLists.txt deleted file mode 100644 index b3cfae9d..00000000 --- a/plugins/signal-protocol/CMakeLists.txt +++ /dev/null @@ -1,91 +0,0 @@ -find_package(GCrypt REQUIRED) -find_packages(SIGNAL_PROTOCOL_PACKAGES REQUIRED - Gee - GLib - GObject -) - -vala_precompile(SIGNAL_PROTOCOL_VALA_C -SOURCES - "src/context.vala" - "src/simple_iks.vala" - "src/simple_ss.vala" - "src/simple_pks.vala" - "src/simple_spks.vala" - "src/store.vala" - "src/util.vala" -CUSTOM_VAPIS - ${CMAKE_CURRENT_SOURCE_DIR}/vapi/signal-protocol-public.vapi - ${CMAKE_CURRENT_SOURCE_DIR}/vapi/signal-protocol-native.vapi -PACKAGES - ${SIGNAL_PROTOCOL_PACKAGES} -GENERATE_VAPI - signal-protocol-vala -GENERATE_HEADER - signal-protocol-vala -) - -set(C_HEADERS_SRC "") -set(C_HEADERS_TARGET "") - -# 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") - -add_custom_command(OUTPUT "${CMAKE_BINARY_DIR}/exports/signal_helper.h" -COMMAND - cp "${CMAKE_CURRENT_SOURCE_DIR}/src/signal_helper.h" "${CMAKE_BINARY_DIR}/exports/signal_helper.h" -DEPENDS - "${CMAKE_CURRENT_SOURCE_DIR}/src/signal_helper.h" -COMMENT - Copy header file signal_helper.h -) - -add_custom_command(OUTPUT ${CMAKE_BINARY_DIR}/exports/signal-protocol.vapi -COMMAND - cat "${CMAKE_CURRENT_SOURCE_DIR}/vapi/signal-protocol-public.vapi" "${CMAKE_BINARY_DIR}/exports/signal-protocol-vala.vapi" > "${CMAKE_BINARY_DIR}/exports/signal-protocol.vapi" -DEPENDS - ${CMAKE_CURRENT_SOURCE_DIR}/vapi/signal-protocol-public.vapi - ${CMAKE_BINARY_DIR}/exports/signal-protocol-vala.vapi -) - -add_custom_target(signal-protocol-vapi -DEPENDS - ${CMAKE_BINARY_DIR}/exports/signal-protocol.vapi - ${CMAKE_BINARY_DIR}/exports/signal-protocol-vala.h - ${C_HEADERS_TARGET} -) - -set(CFLAGS ${VALA_CFLAGS} -I${CMAKE_CURRENT_SOURCE_DIR}/libsignal-protocol-c/src -I${CMAKE_CURRENT_SOURCE_DIR}/src) -add_definitions(${CFLAGS}) -add_library(signal-protocol-vala STATIC ${SIGNAL_PROTOCOL_VALA_C} ${CMAKE_CURRENT_SOURCE_DIR}/src/signal_helper.c) -add_dependencies(signal-protocol-vala signal-protocol-vapi) -target_link_libraries(signal-protocol-vala ${SIGNAL_PROTOCOL_PACKAGES} gcrypt signal-protocol-c m) -set_property(TARGET signal-protocol-vala PROPERTY POSITION_INDEPENDENT_CODE ON) - -if(BUILD_TESTS) - vala_precompile(SIGNAL_TEST_VALA_C - SOURCES - "tests/common.vala" - "tests/testcase.vala" - - "tests/curve25519.vala" - "tests/hkdf.vala" - "tests/session_builder.vala" - CUSTOM_VAPIS - ${CMAKE_BINARY_DIR}/exports/signal-protocol-vala_internal.vapi - ${CMAKE_CURRENT_SOURCE_DIR}/vapi/signal-protocol-public.vapi - ${CMAKE_CURRENT_SOURCE_DIR}/vapi/signal-protocol-native.vapi - PACKAGES - ${SIGNAL_PROTOCOL_PACKAGES} - ) - - set(CFLAGS ${VALA_CFLAGS} -I${CMAKE_CURRENT_BINARY_DIR}/signal-protocol) - add_executable(signal-protocol-vala-test ${SIGNAL_TEST_VALA_C}) - add_dependencies(signal-protocol-vala-test signal-protocol-vala) - target_link_libraries(signal-protocol-vala-test signal-protocol-vala ${SIGNAL_PROTOCOL_PACKAGES}) -endif(BUILD_TESTS) diff --git a/plugins/signal-protocol/vapi/signal-protocol-native.vapi b/plugins/signal-protocol/vapi/signal-protocol-native.vapi deleted file mode 100644 index 0bac0317..00000000 --- a/plugins/signal-protocol/vapi/signal-protocol-native.vapi +++ /dev/null @@ -1,274 +0,0 @@ -namespace Signal { - [Compact] - [CCode (cname = "signal_context", cprefix="signal_context_", free_function="signal_context_destroy", cheader_filename = "signal/signal_protocol.h")] - public class NativeContext { - public static int create(out NativeContext context, void* user_data); - public int set_crypto_provider(NativeCryptoProvider crypto_provider); - public int set_locking_functions(LockingFunc lock, LockingFunc unlock); - public int set_log_function(LogFunc log); - } - [CCode (has_target = false)] - public delegate void LockingFunc(void* user_data); - [CCode (has_target = false)] - public delegate void LogFunc(LogLevel level, string message, size_t len, void* user_data); - - [Compact] - [CCode (cname = "signal_crypto_provider", cheader_filename = "signal/signal_protocol.h")] - public struct NativeCryptoProvider { - public RandomFunc random_func; - public HmacSha256Init hmac_sha256_init_func; - public HmacSha256Update hmac_sha256_update_func; - public HmacSha256Final hmac_sha256_final_func; - public HmacSha256Cleanup hmac_sha256_cleanup_func; - public Sha512DigestInit sha512_digest_init_func; - public Sha512DigestUpdate sha512_digest_update_func; - public Sha512DigestFinal sha512_digest_final_func; - public Sha512DigestCleanup sha512_digest_cleanup_func; - public CryptFunc encrypt_func; - public CryptFunc decrypt_func; - public void* user_data; - } - [CCode (has_target = false)] - public delegate int RandomFunc(uint8[] data, void* user_data); - [CCode (has_target = false)] - public delegate int HmacSha256Init(out void* hmac_context, uint8[] key, void* user_data); - [CCode (has_target = false)] - public delegate int HmacSha256Update(void* hmac_context, uint8[] data, void* user_data); - [CCode (has_target = false)] - public delegate int HmacSha256Final(void* hmac_context, out Buffer buffer, void* user_data); - [CCode (has_target = false)] - public delegate int HmacSha256Cleanup(void* hmac_context, void* user_data); - [CCode (has_target = false)] - public delegate int Sha512DigestInit(out void* digest_context, void* user_data); - [CCode (has_target = false)] - public delegate int Sha512DigestUpdate(void* digest_context, uint8[] data, void* user_data); - [CCode (has_target = false)] - public delegate int Sha512DigestFinal(void* digest_context, out Buffer buffer, void* user_data); - [CCode (has_target = false)] - public delegate int Sha512DigestCleanup(void* digest_context, void* user_data); - [CCode (has_target = false)] - public delegate int CryptFunc(out Buffer output, Cipher cipher, uint8[] key, uint8[] iv, uint8[] content, void* user_data); - - [Compact] - [CCode (cname = "signal_protocol_session_store", cheader_filename = "signal/signal_protocol.h")] - public struct NativeSessionStore { - public LoadSessionFunc load_session_func; - public GetSubDeviceSessionsFunc get_sub_device_sessions_func; - public StoreSessionFunc store_session_func; - public ContainsSessionFunc contains_session_func; - public DeleteSessionFunc delete_session_func; - public DeleteAllSessionsFunc delete_all_sessions_func; - public DestroyFunc destroy_func; - public void* user_data; - } - [CCode (has_target = false)] - public delegate int LoadSessionFunc(out Buffer record, out Buffer user_record, Address address, void* user_data); - [CCode (has_target = false)] - public delegate int GetSubDeviceSessionsFunc(out IntList sessions, [CCode (array_length_type = "size_t")] char[] name, void* user_data); - [CCode (has_target = false)] - public delegate int StoreSessionFunc(Address address, [CCode (array_length_type = "size_t")] uint8[] record, [CCode (array_length_type = "size_t")] uint8[] user_record, void* user_data); - [CCode (has_target = false)] - public delegate int ContainsSessionFunc(Address address, void* user_data); - [CCode (has_target = false)] - public delegate int DeleteSessionFunc(Address address, void* user_data); - [CCode (has_target = false)] - public delegate int DeleteAllSessionsFunc([CCode (array_length_type = "size_t")] char[] name, void* user_data); - - [Compact] - [CCode (cname = "signal_protocol_identity_key_store", cheader_filename = "signal/signal_protocol.h")] - public struct NativeIdentityKeyStore { - GetIdentityKeyPairFunc get_identity_key_pair; - GetLocalRegistrationIdFunc get_local_registration_id; - SaveIdentityFunc save_identity; - IsTrustedIdentityFunc is_trusted_identity; - DestroyFunc destroy_func; - void* user_data; - } - [CCode (has_target = false)] - public delegate int GetIdentityKeyPairFunc(out Buffer public_data, out Buffer private_data, void* user_data); - [CCode (has_target = false)] - public delegate int GetLocalRegistrationIdFunc(void* user_data, out uint32 registration_id); - [CCode (has_target = false)] - public delegate int SaveIdentityFunc(Address address, [CCode (array_length_type = "size_t")] uint8[] key, void* user_data); - [CCode (has_target = false)] - public delegate int IsTrustedIdentityFunc(Address address, [CCode (array_length_type = "size_t")] uint8[] key, void* user_data); - - [Compact] - [CCode (cname = "signal_protocol_pre_key_store", cheader_filename = "signal/signal_protocol.h")] - public struct NativePreKeyStore { - LoadPreKeyFunc load_pre_key; - StorePreKeyFunc store_pre_key; - ContainsPreKeyFunc contains_pre_key; - RemovePreKeyFunc remove_pre_key; - DestroyFunc destroy_func; - void* user_data; - } - [CCode (has_target = false)] - public delegate int LoadPreKeyFunc(out Buffer record, uint32 pre_key_id, void* user_data); - [CCode (has_target = false)] - public delegate int StorePreKeyFunc(uint32 pre_key_id, [CCode (array_length_type = "size_t")] uint8[] record, void* user_data); - [CCode (has_target = false)] - public delegate int ContainsPreKeyFunc(uint32 pre_key_id, void* user_data); - [CCode (has_target = false)] - public delegate int RemovePreKeyFunc(uint32 pre_key_id, void* user_data); - - - [Compact] - [CCode (cname = "signal_protocol_signed_pre_key_store", cheader_filename = "signal/signal_protocol.h")] - public struct NativeSignedPreKeyStore { - LoadPreKeyFunc load_signed_pre_key; - StorePreKeyFunc store_signed_pre_key; - ContainsPreKeyFunc contains_signed_pre_key; - RemovePreKeyFunc remove_signed_pre_key; - DestroyFunc destroy_func; - void* user_data; - } - - - [Compact] - [CCode (cname = "signal_protocol_sender_key_store")] - public struct NativeSenderKeyStore { - StoreSenderKeyFunc store_sender_key; - LoadSenderKeyFunc load_sender_key; - DestroyFunc destroy_func; - void* user_data; - } - [CCode (has_target = false)] - public delegate int StoreSenderKeyFunc(SenderKeyName sender_key_name, [CCode (array_length_type = "size_t")] uint8[] record, [CCode (array_length_type = "size_t")] uint8[] user_record, void* user_data); - [CCode (has_target = false)] - public delegate int LoadSenderKeyFunc(out Buffer record, out Buffer user_record, SenderKeyName sender_key_name, void* user_data); - - [CCode (has_target = false)] - public delegate void DestroyFunc(void* user_data); - - [Compact] - [CCode (cname = "signal_protocol_store_context", cprefix = "signal_protocol_store_context_", free_function="signal_protocol_store_context_destroy", cheader_filename = "signal/signal_protocol.h")] - public class NativeStoreContext { - public static int create(out NativeStoreContext context, NativeContext global_context); - public int set_session_store(NativeSessionStore store); - public int set_pre_key_store(NativePreKeyStore store); - public int set_signed_pre_key_store(NativeSignedPreKeyStore store); - public int set_identity_key_store(NativeIdentityKeyStore store); - public int set_sender_key_store(NativeSenderKeyStore store); - } - - - [CCode (cheader_filename = "signal/signal_protocol.h")] - namespace Protocol { - - /** - * Interface to the pre-key store. - * These functions will use the callbacks in the provided - * signal_protocol_store_context instance and operate in terms of higher level - * library data structures. - */ - [CCode (cprefix = "signal_protocol_pre_key_")] - namespace PreKey { - public int load_key(NativeStoreContext context, out PreKeyRecord pre_key, uint32 pre_key_id); - public int store_key(NativeStoreContext context, PreKeyRecord pre_key); - public int contains_key(NativeStoreContext context, uint32 pre_key_id); - public int remove_key(NativeStoreContext context, uint32 pre_key_id); - } - - [CCode (cprefix = "signal_protocol_signed_pre_key_")] - namespace SignedPreKey { - public int load_key(NativeStoreContext context, out SignedPreKeyRecord pre_key, uint32 pre_key_id); - public int store_key(NativeStoreContext context, SignedPreKeyRecord pre_key); - public int contains_key(NativeStoreContext context, uint32 pre_key_id); - public int remove_key(NativeStoreContext context, uint32 pre_key_id); - } - - /** - * Interface to the session store. - * These functions will use the callbacks in the provided - * signal_protocol_store_context instance and operate in terms of higher level - * library data structures. - */ - [CCode (cprefix = "signal_protocol_session_")] - namespace Session { - public int load_session(NativeStoreContext context, out SessionRecord record, Address address); - public int get_sub_device_sessions(NativeStoreContext context, out IntList sessions, char[] name); - public int store_session(NativeStoreContext context, Address address, SessionRecord record); - public int contains_session(NativeStoreContext context, Address address); - public int delete_session(NativeStoreContext context, Address address); - public int delete_all_sessions(NativeStoreContext context, char[] name); - } - - namespace Identity { - public int get_key_pair(NativeStoreContext store_context, out IdentityKeyPair key_pair); - public int get_local_registration_id(NativeStoreContext store_context, out uint32 registration_id); - public int save_identity(NativeStoreContext store_context, Address address, ECPublicKey identity_key); - public int is_trusted_identity(NativeStoreContext store_context, Address address, ECPublicKey identity_key); - } - - [CCode (cheader_filename = "signal/key_helper.h", cprefix = "signal_protocol_key_helper_")] - namespace KeyHelper { - [Compact] - [CCode (cname = "signal_protocol_key_helper_pre_key_list_node", cprefix = "signal_protocol_key_helper_key_list_", free_function="signal_protocol_key_helper_key_list_free")] - public class PreKeyListNode { - public PreKeyRecord element(); - public PreKeyListNode next(); - } - - public int generate_identity_key_pair(out IdentityKeyPair key_pair, NativeContext global_context); - public int generate_registration_id(out int32 registration_id, int extended_range, NativeContext global_context); - public int get_random_sequence(out int value, int max, NativeContext global_context); - public int generate_pre_keys(out PreKeyListNode head, uint start, uint count, NativeContext global_context); - public int generate_last_resort_pre_key(out PreKeyRecord pre_key, NativeContext global_context); - public int generate_signed_pre_key(out SignedPreKeyRecord signed_pre_key, IdentityKeyPair identity_key_pair, uint32 signed_pre_key_id, uint64 timestamp, NativeContext global_context); - public int generate_sender_signing_key(out ECKeyPair key_pair, NativeContext global_context); - public int generate_sender_key(out Buffer key_buffer, NativeContext global_context); - public int generate_sender_key_id(out int32 key_id, NativeContext global_context); - } - } - - [CCode (cheader_filename = "signal/curve.h")] - namespace Curve { - [CCode (cname = "curve_calculate_agreement")] - public int calculate_agreement([CCode (array_length = false)] out uint8[] shared_key_data, ECPublicKey public_key, ECPrivateKey private_key); - [CCode (cname = "curve_calculate_signature")] - public int calculate_signature(NativeContext context, out Buffer signature, ECPrivateKey signing_key, uint8[] message); - [CCode (cname = "curve_verify_signature")] - public int verify_signature(ECPublicKey signing_key, uint8[] message, uint8[] signature); - } - - [CCode (cname = "session_builder_create", cheader_filename = "signal/session_builder.h")] - public static int session_builder_create(out SessionBuilder builder, NativeStoreContext store, Address remote_address, NativeContext global_context); - [CCode (cname = "session_cipher_create", cheader_filename = "signal/session_cipher.h")] - public static int session_cipher_create(out SessionCipher cipher, NativeStoreContext store, Address remote_address, NativeContext global_context); - [CCode (cname = "pre_key_signal_message_deserialize", cheader_filename = "signal/protocol.h")] - public static int pre_key_signal_message_deserialize(out PreKeySignalMessage message, uint8[] data, NativeContext global_context); - [CCode (cname = "pre_key_signal_message_copy", cheader_filename = "signal/protocol.h")] - public static int pre_key_signal_message_copy(out PreKeySignalMessage message, PreKeySignalMessage other_message, NativeContext global_context); - [CCode (cname = "signal_message_create", cheader_filename = "signal/protocol.h")] - public static int signal_message_create(out SignalMessage message, uint8 message_version, uint8[] mac_key, ECPublicKey sender_ratchet_key, uint32 counter, uint32 previous_counter, uint8[] ciphertext, ECPublicKey sender_identity_key, ECPublicKey receiver_identity_key, NativeContext global_context); - [CCode (cname = "signal_message_deserialize", cheader_filename = "signal/protocol.h")] - public static int signal_message_deserialize(out SignalMessage message, uint8[] data, NativeContext global_context); - [CCode (cname = "signal_message_copy", cheader_filename = "signal/protocol.h")] - public static int signal_message_copy(out SignalMessage message, SignalMessage other_message, NativeContext global_context); - [CCode (cname = "curve_generate_key_pair", cheader_filename = "signal/curve.h")] - public static int curve_generate_key_pair(NativeContext context, out ECKeyPair key_pair); - [CCode (cname = "curve_decode_private_point", cheader_filename = "signal/curve.h")] - public static int curve_decode_private_point(out ECPrivateKey public_key, uint8[] key, NativeContext global_context); - [CCode (cname = "curve_decode_point", cheader_filename = "signal/curve.h")] - public static int curve_decode_point(out ECPublicKey public_key, uint8[] key, NativeContext global_context); - [CCode (cname = "curve_generate_private_key", cheader_filename = "signal/curve.h")] - public static int curve_generate_private_key(NativeContext context, out ECPrivateKey private_key); - [CCode (cname = "ratchet_identity_key_pair_deserialize", cheader_filename = "signal/ratchet.h")] - public static int ratchet_identity_key_pair_deserialize(out IdentityKeyPair key_pair, uint8[] data, NativeContext global_context); - [CCode (cname = "session_signed_pre_key_deserialize", cheader_filename = "signal/signed_pre_key.h")] - public static int session_signed_pre_key_deserialize(out SignedPreKeyRecord pre_key, uint8[] data, NativeContext global_context); - - [Compact] - [CCode (cname = "hkdf_context", cprefix = "hkdf_", free_function = "hkdf_destroy", cheader_filename = "signal/hkdf.h")] - public class NativeHkdfContext { - public static int create(out NativeHkdfContext context, int message_version, NativeContext global_context); - public int compare(NativeHkdfContext other); - public ssize_t derive_secrets([CCode (array_length = false)] out uint8[] output, uint8[] input_key_material, uint8[] salt, uint8[] info, size_t output_len); - } - - [CCode (cname = "setup_signal_vala_crypto_provider", cheader_filename = "signal_helper.h")] - public static void setup_crypto_provider(NativeContext context); - [CCode (cname = "signal_vala_randomize", cheader_filename = "signal_helper.h")] - public static int native_random(uint8[] data); -} diff --git a/plugins/signal-protocol/vapi/signal-protocol-public.vapi b/plugins/signal-protocol/vapi/signal-protocol-public.vapi deleted file mode 100644 index eaf73c0c..00000000 --- a/plugins/signal-protocol/vapi/signal-protocol-public.vapi +++ /dev/null @@ -1,384 +0,0 @@ -namespace Signal { - - [CCode (cname = "int", cprefix = "SG_ERR_", cheader_filename = "signal/signal_protocol.h", has_type_id = false)] - public enum ErrorCode { - [CCode (cname = "SG_SUCCESS")] - SUCCESS, - NOMEM, - INVAL, - UNKNOWN, - DUPLICATE_MESSAGE, - INVALID_KEY, - INVALID_KEY_ID, - INVALID_MAC, - INVALID_MESSAGE, - INVALID_VERSION, - LEGACY_MESSAGE, - NO_SESSION, - STALE_KEY_EXCHANGE, - UNTRUSTED_IDENTITY, - VRF_SIG_VERIF_FAILED, - INVALID_PROTO_BUF, - FP_VERSION_MISMATCH, - FP_IDENT_MISMATCH; - } - - [CCode (cname = "SG_ERR_MINIMUM", cheader_filename = "signal/signal_protocol.h")] - public const int MIN_ERROR_CODE; - - [CCode (cname = "int", cprefix = "SG_LOG_", cheader_filename = "signal/signal_protocol.h", has_type_id = false)] - public enum LogLevel { - ERROR, - WARNING, - NOTICE, - INFO, - DEBUG - } - - [CCode (cname = "signal_throw_gerror_by_code_", cheader_filename = "signal/signal_protocol.h")] - private int throw_by_code(int code, string? message = null) throws GLib.Error { - if (code < 0 && code > MIN_ERROR_CODE) { - throw new GLib.Error(-1, code, "%s: %s", message ?? "Signal error", ((ErrorCode)code).to_string()); - } - return code; - } - - [CCode (cname = "int", cprefix = "SG_CIPHER_", cheader_filename = "signal/signal_protocol.h", has_type_id = false)] - public enum Cipher { - AES_CTR_NOPADDING, - AES_CBC_PKCS5, - AES_GCM_NOPADDING - } - - [Compact] - [CCode (cname = "signal_type_base", ref_function="signal_type_ref_vapi", unref_function="signal_type_unref_vapi", cheader_filename="signal/signal_protocol_types.h,signal_helper.h")] - public class TypeBase { - } - - [Compact] - [CCode (cname = "signal_buffer", cheader_filename = "signal/signal_protocol_types.h", free_function="signal_buffer_free")] - public class Buffer { - [CCode (cname = "signal_buffer_alloc")] - public Buffer(size_t len); - [CCode (cname = "signal_buffer_create")] - public Buffer.from(uint8[] data); - - public Buffer copy(); - public Buffer append(uint8[] data); - public int compare(Buffer other); - - public uint8 get(int i) { return data[i]; } - public void set(int i, uint8 val) { data[i] = val; } - - public uint8[] data { get { int x = (int)len(); unowned uint8[] res = _data(); res.length = x; return res; } } - - [CCode (array_length = false, cname = "signal_buffer_data")] - private unowned uint8[] _data(); - private size_t len(); - } - - [Compact] - [CCode (cname = "signal_int_list", cheader_filename = "signal/signal_protocol_types.h", free_function="signal_int_list_free")] - public class IntList { - [CCode (cname = "signal_int_list_alloc")] - public IntList(); - [CCode (cname = "signal_int_list_push_back")] - public int add(int value); - public uint size { [CCode (cname = "signal_int_list_size")] get; } - [CCode (cname = "signal_int_list_at")] - public int get(uint index); - } - - [Compact] - [CCode (cname = "session_builder", cprefix = "session_builder_", free_function="session_builder_free", cheader_filename = "signal/session_builder.h")] - public class SessionBuilder { - [CCode (cname = "session_builder_process_pre_key_bundle")] - private int process_pre_key_bundle_(PreKeyBundle pre_key_bundle); - [CCode (cname = "session_builder_process_pre_key_bundle_")] - public void process_pre_key_bundle(PreKeyBundle pre_key_bundle) throws GLib.Error { - throw_by_code(process_pre_key_bundle_(pre_key_bundle)); - } - } - - [Compact] - [CCode (cname = "session_pre_key_bundle", cprefix = "session_pre_key_bundle_", cheader_filename = "signal/session_pre_key.h")] - public class PreKeyBundle : TypeBase { - public static int create(out PreKeyBundle bundle, uint32 registration_id, int device_id, uint32 pre_key_id, ECPublicKey? pre_key_public, - uint32 signed_pre_key_id, ECPublicKey? signed_pre_key_public, uint8[]? signed_pre_key_signature, ECPublicKey? identity_key); - public uint32 registration_id { get; } - public int device_id { get; } - public uint32 pre_key_id { get; } - public ECPublicKey pre_key { owned get; } - public uint32 signed_pre_key_id { get; } - public ECPublicKey signed_pre_key { owned get; } - public Buffer signed_pre_key_signature { owned get; } - public ECPublicKey identity_key { owned get; } - } - - [Compact] - [CCode (cname = "session_pre_key", cprefix = "session_pre_key_", cheader_filename = "signal/session_pre_key.h,signal_helper.h")] - public class PreKeyRecord : TypeBase { - public static int create(out PreKeyRecord pre_key, uint32 id, ECKeyPair key_pair); - //public static int deserialize(out PreKeyRecord pre_key, uint8[] data, NativeContext global_context); - [CCode (instance_pos = 2)] - public int serialze(out Buffer buffer); - public uint32 id { get; } - public ECKeyPair key_pair { get; } - } - - [Compact] - [CCode (cname = "session_record", cprefix = "session_record_", cheader_filename = "signal/signal_protocol_types.h")] - public class SessionRecord : TypeBase { - public SessionState state { get; } - public Buffer user_record { get; } - } - - [Compact] - [CCode (cname = "session_state", cprefix = "session_state_", cheader_filename = "signal/session_state.h")] - public class SessionState : TypeBase { - //public static int create(out SessionState state, NativeContext context); - //public static int deserialize(out SessionState state, uint8[] data, NativeContext context); - //public static int copy(out SessionState state, SessionState other_state, NativeContext context); - [CCode (instance_pos = 2)] - public int serialze(out Buffer buffer); - - public uint32 session_version { get; set; } - public ECPublicKey local_identity_key { get; set; } - public ECPublicKey remote_identity_key { get; set; } - //public Ratchet.RootKey root_key { get; set; } - public uint32 previous_counter { get; set; } - public ECPublicKey sender_ratchet_key { get; } - public ECKeyPair sender_ratchet_key_pair { get; } - //public Ratchet.ChainKey sender_chain_key { get; set; } - public uint32 remote_registration_id { get; set; } - public uint32 local_registration_id { get; set; } - public int needs_refresh { get; set; } - public ECPublicKey alice_base_key { get; set; } - } - - [Compact] - [CCode (cname = "session_signed_pre_key", cprefix = "session_signed_pre_key_", cheader_filename = "signal/session_pre_key.h")] - public class SignedPreKeyRecord : TypeBase { - public static int create(out SignedPreKeyRecord pre_key, uint32 id, uint64 timestamp, ECKeyPair key_pair, uint8[] signature); - [CCode (instance_pos = 2)] - public int serialze(out Buffer buffer); - - public uint32 id { get; } - public uint64 timestamp { get; } - public ECKeyPair key_pair { get; } - public uint8[] signature { [CCode (cname = "session_signed_pre_key_get_signature_")] get { int x = (int)get_signature_len(); unowned uint8[] res = get_signature(); res.length = x; return res; } } - - [CCode (array_length = false, cname = "session_signed_pre_key_get_signature")] - private unowned uint8[] get_signature(); - private size_t get_signature_len(); - } - - /** - * Address of an Signal Protocol message recipient - */ - [Compact] - [CCode (cname = "signal_protocol_address", cprefix = "signal_protocol_address_", cheader_filename = "signal/signal_protocol.h,signal_helper.h")] - public class Address { - public Address(string name, int32 device_id); - public int32 device_id { get; set; } - public string name { owned get; set; } - } - - /** - * A representation of a (group + sender + device) tuple - */ - [Compact] - [CCode (cname = "signal_protocol_sender_key_name")] - public class SenderKeyName { - [CCode (cname = "group_id", array_length_cname="group_id_len")] - private char* group_id_; - private size_t group_id_len; - public Address sender; - } - - [Compact] - [CCode (cname = "ec_public_key", cprefix = "ec_public_key_", cheader_filename = "signal/curve.h,signal_helper.h")] - public class ECPublicKey : TypeBase { - [CCode (cname = "curve_generate_public_key")] - public static int generate(out ECPublicKey public_key, ECPrivateKey private_key); - [CCode (instance_pos = 1, cname = "ec_public_key_serialize")] - private int serialize_([CCode (pos = 0)] out Buffer buffer); - [CCode (cname = "ec_public_key_serialize_")] - public uint8[] serialize() { - Buffer buffer; - int code = serialize_(out buffer); - if (code < 0 && code > MIN_ERROR_CODE) { - // Can only throw for invalid arguments or out of memory. - GLib.assert_not_reached(); - } - return buffer.data; - } - public int compare(ECPublicKey other); - public int memcmp(ECPublicKey other); - } - - [Compact] - [CCode (cname = "ec_private_key", cprefix = "ec_private_key_", cheader_filename = "signal/curve.h,signal_helper.h")] - public class ECPrivateKey : TypeBase { - [CCode (instance_pos = 1, cname = "ec_private_key_serialize")] - private int serialize_([CCode (pos = 0)] out Buffer buffer); - [CCode (cname = "ec_private_key_serialize_")] - public uint8[] serialize() throws GLib.Error { - Buffer buffer; - int code = serialize_(out buffer); - if (code < 0 && code > MIN_ERROR_CODE) { - // Can only throw for invalid arguments or out of memory. - GLib.assert_not_reached(); - } - return buffer.data; - } - public int compare(ECPublicKey other); - } - - [Compact] - [CCode (cname = "ec_key_pair", cprefix="ec_key_pair_", cheader_filename = "signal/curve.h,signal_helper.h")] - public class ECKeyPair : TypeBase { - public static int create(out ECKeyPair key_pair, ECPublicKey public_key, ECPrivateKey private_key); - public ECPublicKey public { get; } - public ECPrivateKey private { get; } - } - - [CCode (cname = "ratchet_message_keys", cheader_filename = "signal/ratchet.h")] - public class MessageKeys { - } - - [Compact] - [CCode (cname = "ratchet_identity_key_pair", cprefix = "ratchet_identity_key_pair_", cheader_filename = "signal/ratchet.h,signal_helper.h")] - public class IdentityKeyPair : TypeBase { - public static int create(out IdentityKeyPair key_pair, ECPublicKey public_key, ECPrivateKey private_key); - public int serialze(out Buffer buffer); - public ECPublicKey public { get; } - public ECPrivateKey private { get; } - } - - [Compact] - [CCode (cname = "ec_public_key_list")] - public class PublicKeyList {} - - /** - * The main entry point for Signal Protocol encrypt/decrypt operations. - * - * Once a session has been established with session_builder, - * this class can be used for all encrypt/decrypt operations within - * that session. - */ - [Compact] - [CCode (cname = "session_cipher", cprefix = "session_cipher_", cheader_filename = "signal/session_cipher.h", free_function = "session_cipher_free")] - public class SessionCipher { - public void* user_data { get; set; } - public DecryptionCallback decryption_callback { set; } - [CCode (cname = "session_cipher_encrypt")] - private int encrypt_(uint8[] padded_message, out CiphertextMessage encrypted_message); - [CCode (cname = "session_cipher_encrypt_")] - public CiphertextMessage encrypt(uint8[] padded_message) throws GLib.Error { - CiphertextMessage res; - throw_by_code(encrypt_(padded_message, out res)); - return res; - } - [CCode (cname = "session_cipher_decrypt_pre_key_signal_message")] - private int decrypt_pre_key_signal_message_(PreKeySignalMessage ciphertext, void* decrypt_context, out Buffer plaintext); - [CCode (cname = "session_cipher_decrypt_pre_key_signal_message_")] - public uint8[] decrypt_pre_key_signal_message(PreKeySignalMessage ciphertext, void* decrypt_context = null) throws GLib.Error { - Buffer res; - throw_by_code(decrypt_pre_key_signal_message_(ciphertext, decrypt_context, out res)); - return res.data; - } - [CCode (cname = "session_cipher_decrypt_signal_message")] - private int decrypt_signal_message_(SignalMessage ciphertext, void* decrypt_context, out Buffer plaintext); - [CCode (cname = "session_cipher_decrypt_signal_message_")] - public uint8[] decrypt_signal_message(SignalMessage ciphertext, void* decrypt_context = null) throws GLib.Error { - Buffer res; - throw_by_code(decrypt_signal_message_(ciphertext, decrypt_context, out res)); - return res.data; - } - public int get_remote_registration_id(out uint32 remote_id); - public int get_session_version(uint32 version); - - [CCode (has_target = false)] - public delegate int DecryptionCallback(SessionCipher cipher, Buffer plaintext, void* decrypt_context); - } - - [CCode (cname = "int", cheader_filename = "signal/protocol.h", has_type_id = false)] - public enum CiphertextType { - [CCode (cname = "CIPHERTEXT_SIGNAL_TYPE")] - SIGNAL, - [CCode (cname = "CIPHERTEXT_PREKEY_TYPE")] - PREKEY, - [CCode (cname = "CIPHERTEXT_SENDERKEY_TYPE")] - SENDERKEY, - [CCode (cname = "CIPHERTEXT_SENDERKEY_DISTRIBUTION_TYPE")] - SENDERKEY_DISTRIBUTION - } - - [Compact] - [CCode (cname = "ciphertext_message", cprefix = "ciphertext_message_", cheader_filename = "signal/protocol.h,signal_helper.h")] - public abstract class CiphertextMessage : TypeBase { - public CiphertextType type { get; } - [CCode (cname = "ciphertext_message_get_serialized")] - private unowned Buffer get_serialized_(); - public uint8[] serialized { [CCode (cname = "ciphertext_message_get_serialized_")] get { - return get_serialized_().data; - }} - } - [Compact] - [CCode (cname = "signal_message", cprefix = "signal_message_", cheader_filename = "signal/protocol.h,signal_helper.h")] - public class SignalMessage : CiphertextMessage { - public ECPublicKey sender_ratchet_key { get; } - public uint8 message_version { get; } - public uint32 counter { get; } - public Buffer body { get; } - //public int verify_mac(uint8 message_version, ECPublicKey sender_identity_key, ECPublicKey receiver_identity_key, uint8[] mac, NativeContext global_context); - public static int is_legacy(uint8[] data); - } - [Compact] - [CCode (cname = "pre_key_signal_message", cprefix = "pre_key_signal_message_", cheader_filename = "signal/protocol.h,signal_helper.h")] - public class PreKeySignalMessage : CiphertextMessage { - public uint8 message_version { get; } - public ECPublicKey identity_key { get; } - public uint32 registration_id { get; } - public uint32 pre_key_id { get; } - public uint32 signed_pre_key_id { get; } - public ECPublicKey base_key { get; } - public SignalMessage signal_message { get; } - } - [Compact] - [CCode (cname = "sender_key_message", cprefix = "sender_key_message_", cheader_filename = "signal/protocol.h,signal_helper.h")] - public class SenderKeyMessage : CiphertextMessage { - public uint32 key_id { get; } - public uint32 iteration { get; } - public Buffer ciphertext { get; } - } - [Compact] - [CCode (cname = "sender_key_distribution_message", cprefix = "sender_key_distribution_message_", cheader_filename = "signal/protocol.h,signal_helper.h")] - public class SenderKeyDistributionMessage : CiphertextMessage { - public uint32 id { get; } - public uint32 iteration { get; } - public Buffer chain_key { get; } - public ECPublicKey signature_key { get; } - } - - [CCode (cname = "signal_vala_encrypt", cheader_filename = "signal_helper.h")] - private static int aes_encrypt_(out Buffer output, int cipher, uint8[] key, uint8[] iv, uint8[] plaintext, void *user_data); - - [CCode (cname = "signal_vala_encrypt_")] - public uint8[] aes_encrypt(int cipher, uint8[] key, uint8[] iv, uint8[] plaintext) throws GLib.Error { - Buffer buf; - throw_by_code(aes_encrypt_(out buf, cipher, key, iv, plaintext, null)); - return buf.data; - } - - [CCode (cname = "signal_vala_decrypt", cheader_filename = "signal_helper.h")] - private static int aes_decrypt_(out Buffer output, int cipher, uint8[] key, uint8[] iv, uint8[] ciphertext, void *user_data); - - [CCode (cname = "signal_vala_decrypt_")] - public uint8[] aes_decrypt(int cipher, uint8[] key, uint8[] iv, uint8[] ciphertext) throws GLib.Error { - Buffer buf; - throw_by_code(aes_decrypt_(out buf, cipher, key, iv, ciphertext, null)); - return buf.data; - } -} diff --git a/qlite/meson.build b/qlite/meson.build index 714a4224..9523b618 100644 --- a/qlite/meson.build +++ b/qlite/meson.build @@ -18,5 +18,10 @@ sources = files( c_args = [ '-DG_LOG_DOMAIN="qlite"', ] -lib_qlite = library('qlite', sources, c_args: c_args, vala_args: ['--vapidir', meson.current_source_dir() / 'vapi'], dependencies: dependencies) +vala_args = [ + '--vapidir', meson.current_source_dir() / 'vapi', +] +lib_qlite = library('qlite', sources, c_args: c_args, vala_args: vala_args, dependencies: dependencies, version: '0.1', install: true, install_dir: [true, true, true]) dep_qlite = declare_dependency(link_with: lib_qlite, include_directories: include_directories('.')) + +install_data('qlite.deps', install_dir: get_option('datadir') / 'vala/vapi') # TODO: workaround for https://github.com/mesonbuild/meson/issues/9756 diff --git a/qlite/qlite.deps b/qlite/qlite.deps new file mode 100644 index 00000000..d9b15e78 --- /dev/null +++ b/qlite/qlite.deps @@ -0,0 +1,3 @@ +gee-0.8 +glib-2.0 +sqlite3 diff --git a/xmpp-vala/CMakeLists.txt b/xmpp-vala/CMakeLists.txt index 39c090fe..cfbc0aaf 100644 --- a/xmpp-vala/CMakeLists.txt +++ b/xmpp-vala/CMakeLists.txt @@ -9,6 +9,11 @@ find_packages(ENGINE_PACKAGES REQUIRED set(ENGINE_EXTRA_OPTIONS ${MAIN_EXTRA_OPTIONS} --vapidir=${CMAKE_CURRENT_SOURCE_DIR}/vapi) +set(ENGINE_DEFINITIONS) +if(VALA_VERSION VERSION_EQUAL "0.56.11") + set(ENGINE_DEFINITIONS ${ENGINE_DEFINITIONS} VALA_0_56_11) +endif() + vala_precompile(ENGINE_VALA_C SOURCES "src/core/direct_tls_xmpp_stream.vala" @@ -152,6 +157,8 @@ CUSTOM_VAPIS "${CMAKE_CURRENT_SOURCE_DIR}/src/glib_fixes.vapi" OPTIONS ${ENGINE_EXTRA_OPTIONS} +DEFINITIONS + ${ENGINE_DEFINITIONS} ) add_custom_target(xmpp-vala-vapi diff --git a/xmpp-vala/meson.build b/xmpp-vala/meson.build index 3064339a..be5e96a8 100644 --- a/xmpp-vala/meson.build +++ b/xmpp-vala/meson.build @@ -129,5 +129,10 @@ sources = files( c_args = [ '-DG_LOG_DOMAIN="xmpp-vala"', ] -lib_xmpp_vala = library('xmpp-vala', sources, c_args: c_args, vala_args: ['--vapidir', meson.current_source_dir() / 'vapi'], dependencies: dependencies) +vala_args = [ + '--vapidir', meson.current_source_dir() / 'vapi', +] +lib_xmpp_vala = library('xmpp-vala', sources, c_args: c_args, vala_args: vala_args, dependencies: dependencies, version: '0.1', install: true, install_dir: [true, true, true]) dep_xmpp_vala = declare_dependency(link_with: lib_xmpp_vala, include_directories: include_directories('.')) + +install_data('xmpp-vala.deps', install_dir: get_option('datadir') / 'vala/vapi') # TODO: workaround for https://github.com/mesonbuild/meson/issues/9756 diff --git a/xmpp-vala/src/core/module_flag.vala b/xmpp-vala/src/core/module_flag.vala index 95547852..76ae4dc1 100644 --- a/xmpp-vala/src/core/module_flag.vala +++ b/xmpp-vala/src/core/module_flag.vala @@ -10,7 +10,12 @@ namespace Xmpp { } public T? cast(XmppStreamFlag flag) { +#if VALA_0_56_11 + // We can't typecheck due to compiler bug + return (T) module; +#else return flag.get_type().is_a(typeof(T)) ? (T?) flag : null; +#endif } public bool matches(XmppStreamFlag module) { @@ -34,7 +39,12 @@ namespace Xmpp { } public T? cast(XmppStreamModule module) { +#if VALA_0_56_11 + // We can't typecheck due to compiler bug + return (T) module; +#else return module.get_type().is_a(typeof(T)) ? (T?) module : null; +#endif } public bool matches(XmppStreamModule module) { diff --git a/xmpp-vala/src/core/xmpp_stream.vala b/xmpp-vala/src/core/xmpp_stream.vala index 42e90bf9..54433b67 100644 --- a/xmpp-vala/src/core/xmpp_stream.vala +++ b/xmpp-vala/src/core/xmpp_stream.vala @@ -30,9 +30,9 @@ public abstract class Xmpp.XmppStream : Object { this.remote_name = remote_name; } - public abstract async void connect() throws IOError; + public abstract new async void connect() throws IOError; - public abstract async void disconnect() throws IOError; + public abstract new async void disconnect() throws IOError; public abstract async StanzaNode read() throws IOError; diff --git a/xmpp-vala/src/module/xep/0004_data_forms.vala b/xmpp-vala/src/module/xep/0004_data_forms.vala index fe39874a..6b5624da 100644 --- a/xmpp-vala/src/module/xep/0004_data_forms.vala +++ b/xmpp-vala/src/module/xep/0004_data_forms.vala @@ -38,7 +38,7 @@ public class DataForm { } } - public class Field { + public class Field : Object { public StanzaNode node { get; set; } public string? label { get { return node.get_attribute("label", NS_URI); } diff --git a/xmpp-vala/src/module/xep/0045_muc/module.vala b/xmpp-vala/src/module/xep/0045_muc/module.vala index f8ddb6d0..2d7ddd14 100644 --- a/xmpp-vala/src/module/xep/0045_muc/module.vala +++ b/xmpp-vala/src/module/xep/0045_muc/module.vala @@ -54,6 +54,11 @@ public enum Feature { UNSECURED } +public static void add_muc_pm_message_stanza_x_node(MessageStanza message_stanza) { + StanzaNode x_node = new StanzaNode.build("x", "http://jabber.org/protocol/muc#user").add_self_xmlns(); + message_stanza.stanza.put_node(x_node); +} + public class JoinResult { public MucEnterError? muc_error; public string? stanza_error; @@ -203,6 +208,8 @@ public class Module : XmppStreamModule { case Affiliation.ADMIN: if (other_affiliation == Affiliation.OWNER) return false; break; + case Affiliation.OWNER: + return true; default: return false; } diff --git a/xmpp-vala/src/module/xep/0082_date_time_profiles.vala b/xmpp-vala/src/module/xep/0082_date_time_profiles.vala index 32d4d3ac..8b40d3ac 100644 --- a/xmpp-vala/src/module/xep/0082_date_time_profiles.vala +++ b/xmpp-vala/src/module/xep/0082_date_time_profiles.vala @@ -1,23 +1,11 @@ namespace Xmpp.Xep.DateTimeProfiles { public DateTime? parse_string(string time_string) { - // TODO with glib >= 2.56 - // return new DateTime.from_iso8601(time_string, null); - - TimeVal time_val = TimeVal(); - if (time_val.from_iso8601(time_string)) { - return new DateTime.from_unix_utc(time_val.tv_sec); - } - return null; - + return new DateTime.from_iso8601(time_string, null); } - public string to_datetime(DateTime time) { - // TODO with glib >= 2.62 - // return time.to_utc().format_iso8601().to_string(); - - return time.to_utc().format("%Y-%m-%dT%H:%M:%SZ"); + return time.to_utc().format_iso8601().to_string(); } } diff --git a/xmpp-vala/src/module/xep/0384_omemo/omemo_encryptor.vala b/xmpp-vala/src/module/xep/0384_omemo/omemo_encryptor.vala index 6509bfe3..c68de329 100644 --- a/xmpp-vala/src/module/xep/0384_omemo/omemo_encryptor.vala +++ b/xmpp-vala/src/module/xep/0384_omemo/omemo_encryptor.vala @@ -72,27 +72,27 @@ namespace Xmpp.Xep.Omemo { } public class EncryptionResult { - public int lost { get; internal set; } - public int success { get; internal set; } - public int unknown { get; internal set; } - public int failure { get; internal set; } + public int lost { get; set; } + public int success { get; set; } + public int unknown { get; set; } + public int failure { get; set; } } public class EncryptState { - public bool encrypted { get; internal set; } - public int other_devices { get; internal set; } - public int other_success { get; internal set; } - public int other_lost { get; internal set; } - public int other_unknown { get; internal set; } - public int other_failure { get; internal set; } - public int other_waiting_lists { get; internal set; } + public bool encrypted { get; set; } + public int other_devices { get; set; } + public int other_success { get; set; } + public int other_lost { get; set; } + public int other_unknown { get; set; } + public int other_failure { get; set; } + public int other_waiting_lists { get; set; } - public int own_devices { get; internal set; } - public int own_success { get; internal set; } - public int own_lost { get; internal set; } - public int own_unknown { get; internal set; } - public int own_failure { get; internal set; } - public bool own_list { get; internal set; } + public int own_devices { get; set; } + public int own_success { get; set; } + public int own_lost { get; set; } + public int own_unknown { get; set; } + public int own_failure { get; set; } + public bool own_list { get; set; } public void add_result(EncryptionResult enc_res, bool own) { if (own) { diff --git a/xmpp-vala/xmpp-vala.deps b/xmpp-vala/xmpp-vala.deps new file mode 100644 index 00000000..97323d51 --- /dev/null +++ b/xmpp-vala/xmpp-vala.deps @@ -0,0 +1,5 @@ +gdk-pixbuf-2.0 +gee-0.8 +gio-2.0 +glib-2.0 +icu-uc