diff --git a/CMakeLists.txt b/CMakeLists.txt index 590398c5..518555bc 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -206,7 +206,11 @@ include(${VALA_USE_FILE}) include(MultiFind) include(GlibCompileResourcesSupport) -set(CMAKE_VALA_FLAGS "${CMAKE_VALA_FLAGS} --target-glib=${GLib_GLOBAL_VERSION}") +find_package(GLib ${GLib_GLOBAL_VERSION} REQUIRED) +string(REGEX REPLACE "^([0-9]+)\\.[0-9]+(\\.[0-9]+)?" "\\1" GLib_MAJOR_VERSION "${GLib_VERSION}") +string(REGEX REPLACE "^[0-9]+\\.([0-9]+)(\\.[0-9]+)?" "\\1" GLib_MINOR_VERSION "${GLib_VERSION}") +math(EXPR GLib_LAST_RELEASE_MINOR_VERSION "${GLib_MINOR_VERSION} / 2 * 2") +set(CMAKE_VALA_FLAGS "${CMAKE_VALA_FLAGS} --target-glib=${GLib_MAJOR_VERSION}.${GLib_LAST_RELEASE_MINOR_VERSION}") add_subdirectory(qlite) add_subdirectory(xmpp-vala) diff --git a/cmake/FindSoup.cmake b/cmake/FindSoup.cmake deleted file mode 100644 index d5afab48..00000000 --- a/cmake/FindSoup.cmake +++ /dev/null @@ -1,31 +0,0 @@ -include(PkgConfigWithFallback) -find_pkg_config_with_fallback(Soup - PKG_CONFIG_NAME libsoup-2.4 - LIB_NAMES soup-2.4 - INCLUDE_NAMES libsoup/soup.h - INCLUDE_DIR_SUFFIXES libsoup-2.4 libsoup-2.4/include libsoup libsoup/include - DEPENDS GIO -) - -if(Soup_FOUND AND NOT Soup_VERSION) - find_file(Soup_VERSION_HEADER "libsoup/soup-version.h" HINTS ${Soup_INCLUDE_DIRS}) - mark_as_advanced(Soup_VERSION_HEADER) - - if(Soup_VERSION_HEADER) - file(STRINGS "${Soup_VERSION_HEADER}" Soup_MAJOR_VERSION REGEX "^#define SOUP_MAJOR_VERSION +\\(?([0-9]+)\\)?$") - string(REGEX REPLACE "^#define SOUP_MAJOR_VERSION \\(?([0-9]+)\\)?$" "\\1" Soup_MAJOR_VERSION "${Soup_MAJOR_VERSION}") - file(STRINGS "${Soup_VERSION_HEADER}" Soup_MINOR_VERSION REGEX "^#define SOUP_MINOR_VERSION +\\(?([0-9]+)\\)?$") - string(REGEX REPLACE "^#define SOUP_MINOR_VERSION \\(?([0-9]+)\\)?$" "\\1" Soup_MINOR_VERSION "${Soup_MINOR_VERSION}") - file(STRINGS "${Soup_VERSION_HEADER}" Soup_MICRO_VERSION REGEX "^#define SOUP_MICRO_VERSION +\\(?([0-9]+)\\)?$") - string(REGEX REPLACE "^#define SOUP_MICRO_VERSION \\(?([0-9]+)\\)?$" "\\1" Soup_MICRO_VERSION "${Soup_MICRO_VERSION}") - set(Soup_VERSION "${Soup_MAJOR_VERSION}.${Soup_MINOR_VERSION}.${Soup_MICRO_VERSION}") - unset(Soup_MAJOR_VERSION) - unset(Soup_MINOR_VERSION) - unset(Soup_MICRO_VERSION) - endif() -endif() - -include(FindPackageHandleStandardArgs) -find_package_handle_standard_args(Soup - REQUIRED_VARS Soup_LIBRARY - VERSION_VAR Soup_VERSION) diff --git a/cmake/FindSoup2.cmake b/cmake/FindSoup2.cmake new file mode 100644 index 00000000..07ffed14 --- /dev/null +++ b/cmake/FindSoup2.cmake @@ -0,0 +1,31 @@ +include(PkgConfigWithFallback) +find_pkg_config_with_fallback(Soup2 + PKG_CONFIG_NAME libsoup-2.4 + LIB_NAMES soup-2.4 + INCLUDE_NAMES libsoup/soup.h + INCLUDE_DIR_SUFFIXES libsoup-2.4 libsoup-2.4/include libsoup libsoup/include + DEPENDS GIO +) + +if(Soup2_FOUND AND NOT Soup2_VERSION) + find_file(Soup2_VERSION_HEADER "libsoup/soup-version.h" HINTS ${Soup_INCLUDE_DIRS}) + mark_as_advanced(Soup2_VERSION_HEADER) + + if(Soup_VERSION_HEADER) + file(STRINGS "${Soup2_VERSION_HEADER}" Soup2_MAJOR_VERSION REGEX "^#define SOUP_MAJOR_VERSION +\\(?([0-9]+)\\)?$") + string(REGEX REPLACE "^#define SOUP_MAJOR_VERSION \\(?([0-9]+)\\)?$" "\\1" Soup_MAJOR_VERSION "${Soup2_MAJOR_VERSION}") + file(STRINGS "${Soup2_VERSION_HEADER}" Soup2_MINOR_VERSION REGEX "^#define SOUP_MINOR_VERSION +\\(?([0-9]+)\\)?$") + string(REGEX REPLACE "^#define SOUP_MINOR_VERSION \\(?([0-9]+)\\)?$" "\\1" Soup_MINOR_VERSION "${Soup2_MINOR_VERSION}") + file(STRINGS "${Soup2_VERSION_HEADER}" Soup2_MICRO_VERSION REGEX "^#define SOUP_MICRO_VERSION +\\(?([0-9]+)\\)?$") + string(REGEX REPLACE "^#define SOUP_MICRO_VERSION \\(?([0-9]+)\\)?$" "\\1" Soup_MICRO_VERSION "${Soup2_MICRO_VERSION}") + set(Soup_VERSION "${Soup2_MAJOR_VERSION}.${Soup2_MINOR_VERSION}.${Soup2_MICRO_VERSION}") + unset(Soup2_MAJOR_VERSION) + unset(Soup2_MINOR_VERSION) + unset(Soup2_MICRO_VERSION) + endif() +endif() + +include(FindPackageHandleStandardArgs) +find_package_handle_standard_args(Soup2 + REQUIRED_VARS Soup2_LIBRARY + VERSION_VAR Soup2_VERSION) diff --git a/cmake/FindSoup3.cmake b/cmake/FindSoup3.cmake new file mode 100644 index 00000000..07b4893a --- /dev/null +++ b/cmake/FindSoup3.cmake @@ -0,0 +1,31 @@ +include(PkgConfigWithFallback) +find_pkg_config_with_fallback(Soup3 + PKG_CONFIG_NAME libsoup-3.0 + LIB_NAMES soup-3.0 + INCLUDE_NAMES libsoup/soup.h + INCLUDE_DIR_SUFFIXES libsoup-2.4 libsoup-2.4/include libsoup libsoup/include + DEPENDS GIO +) + +if(Soup3_FOUND AND NOT Soup3_VERSION) + find_file(Soup3_VERSION_HEADER "libsoup/soup-version.h" HINTS ${Soup3_INCLUDE_DIRS}) + mark_as_advanced(Soup3_VERSION_HEADER) + + if(Soup3_VERSION_HEADER) + file(STRINGS "${Soup3_VERSION_HEADER}" Soup3_MAJOR_VERSION REGEX "^#define SOUP_MAJOR_VERSION +\\(?([0-9]+)\\)?$") + string(REGEX REPLACE "^#define SOUP_MAJOR_VERSION \\(?([0-9]+)\\)?$" "\\1" Soup3_MAJOR_VERSION "${Soup3_MAJOR_VERSION}") + file(STRINGS "${Soup3_VERSION_HEADER}" Soup3_MINOR_VERSION REGEX "^#define SOUP_MINOR_VERSION +\\(?([0-9]+)\\)?$") + string(REGEX REPLACE "^#define SOUP_MINOR_VERSION \\(?([0-9]+)\\)?$" "\\1" Soup3_MINOR_VERSION "${Soup3_MINOR_VERSION}") + file(STRINGS "${Soup3_VERSION_HEADER}" Soup3_MICRO_VERSION REGEX "^#define SOUP_MICRO_VERSION +\\(?([0-9]+)\\)?$") + string(REGEX REPLACE "^#define SOUP_MICRO_VERSION \\(?([0-9]+)\\)?$" "\\1" Soup3_MICRO_VERSION "${Soup3_MICRO_VERSION}") + set(Soup3_VERSION "${Soup3_MAJOR_VERSION}.${Soup3_MINOR_VERSION}.${Soup3_MICRO_VERSION}") + unset(Soup3_MAJOR_VERSION) + unset(Soup3_MINOR_VERSION) + unset(Soup3_MICRO_VERSION) + endif() +endif() + +include(FindPackageHandleStandardArgs) +find_package_handle_standard_args(Soup3 + REQUIRED_VARS Soup3_LIBRARY + VERSION_VAR Soup3_VERSION) diff --git a/dino.doap b/dino.doap index ba5814e3..819777b0 100644 --- a/dino.doap +++ b/dino.doap @@ -250,11 +250,15 @@ + complete + For use with XEP-0261 + deprecated + Migrating to XEP-0402 if supported by server @@ -273,6 +277,8 @@ + partial + For use with XEP-0313 @@ -284,6 +290,7 @@ + partial For use with XEP-0260 @@ -291,6 +298,7 @@ complete + For file transfers using XEP-0363 @@ -326,27 +334,32 @@ + deprecated + Only to fetch Avatars from other users - partial + complete + partial + partial + partial @@ -358,7 +371,7 @@ - partial + complete @@ -382,21 +395,25 @@ + partial + complete + complete + partial @@ -409,11 +426,14 @@ + partial + No support for sending + complete @@ -431,21 +451,26 @@ + partial + partial + complete + For use with XEP-0280 + complete 1.2.0 0.2 @@ -460,6 +485,8 @@ + complete + 1.0.0 @@ -471,16 +498,20 @@ + partial + complete + 0.3.1 + complete @@ -506,11 +537,13 @@ complete + 0.3.0 + partial @@ -522,6 +555,7 @@ + complete @@ -533,16 +567,20 @@ + complete + complete + partial + No support for embedded thumbnails diff --git a/dino.doap.in b/dino.doap.in index 941fd11b..563de1d4 100644 --- a/dino.doap.in +++ b/dino.doap.in @@ -70,11 +70,15 @@ + complete + For use with XEP-0261 + deprecated + Migrating to XEP-0402 if supported by server @@ -93,6 +97,8 @@ + partial + For use with XEP-0313 @@ -104,6 +110,7 @@ + partial For use with XEP-0260 @@ -111,6 +118,7 @@ complete + For file transfers using XEP-0363 @@ -146,27 +154,32 @@ + deprecated + Only to fetch Avatars from other users - partial + complete + partial + partial + partial @@ -178,7 +191,7 @@ - partial + complete @@ -202,21 +215,25 @@ + partial + complete + complete + partial @@ -229,11 +246,14 @@ + partial + No support for sending + complete @@ -251,21 +271,26 @@ + partial + partial + complete + For use with XEP-0280 + complete 1.2.0 0.2 @@ -280,6 +305,8 @@ + complete + 1.0.0 @@ -291,16 +318,20 @@ + partial + complete + 0.3.1 + complete @@ -326,11 +357,13 @@ complete + 0.3.0 + partial @@ -342,6 +375,7 @@ + complete @@ -353,16 +387,20 @@ + complete + complete + partial + No support for embedded thumbnails diff --git a/libdino/CMakeLists.txt b/libdino/CMakeLists.txt index ce836f62..20f5ffee 100644 --- a/libdino/CMakeLists.txt +++ b/libdino/CMakeLists.txt @@ -89,7 +89,7 @@ DEPENDS ${CMAKE_BINARY_DIR}/exports/dino_i18n.h ) -add_definitions(${VALA_CFLAGS} -DDINO_SYSTEM_PLUGIN_DIR="${PLUGIN_INSTALL_DIR}" -DDINO_SYSTEM_LIBDIR_NAME="${LIBDIR_NAME}" -DG_LOG_DOMAIN="libdino") +add_definitions(${VALA_CFLAGS} -DDINO_SYSTEM_PLUGIN_DIR="${PLUGIN_INSTALL_DIR}" -DDINO_SYSTEM_LIBDIR_NAME="${LIBDIR_NAME}" -DG_LOG_DOMAIN="libdino" -DDINO_VERSION=\"${PROJECT_VERSION}\") add_library(libdino SHARED ${LIBDINO_VALA_C} ${CMAKE_BINARY_DIR}/exports/dino_i18n.h) add_dependencies(libdino dino-vapi) target_link_libraries(libdino xmpp-vala qlite ${LIBDINO_PACKAGES} m) diff --git a/libdino/src/application.vala b/libdino/src/application.vala index f381c21d..9b36dd79 100644 --- a/libdino/src/application.vala +++ b/libdino/src/application.vala @@ -1,7 +1,13 @@ using Dino.Entities; namespace Dino { + extern const string VERSION; +public string get_version() { return VERSION; } +public string get_short_version() { + if (!VERSION.contains("~")) return VERSION; + return VERSION.split("~")[0] + "+"; +} public interface Application : GLib.Application { diff --git a/libdino/src/entity/file_transfer.vala b/libdino/src/entity/file_transfer.vala index 1823478f..20bc1a7a 100644 --- a/libdino/src/entity/file_transfer.vala +++ b/libdino/src/entity/file_transfer.vala @@ -70,6 +70,7 @@ public class FileTransfer : Object { public State state { get; set; default=State.NOT_STARTED; } public int provider { get; set; } public string info { get; set; } + public Cancellable cancellable { get; default=new Cancellable(); } private Database? db; private string storage_dir; diff --git a/libdino/src/service/file_manager.vala b/libdino/src/service/file_manager.vala index 6249f7ab..95afc45a 100644 --- a/libdino/src/service/file_manager.vala +++ b/libdino/src/service/file_manager.vala @@ -246,7 +246,7 @@ public class FileManager : StreamInteractionModule, Object { File file = File.new_for_path(Path.build_filename(get_storage_dir(), filename)); OutputStream os = file.create(FileCreateFlags.REPLACE_DESTINATION); - yield os.splice_async(input_stream, OutputStreamSpliceFlags.CLOSE_SOURCE|OutputStreamSpliceFlags.CLOSE_TARGET); + yield os.splice_async(input_stream, OutputStreamSpliceFlags.CLOSE_SOURCE | OutputStreamSpliceFlags.CLOSE_TARGET, Priority.LOW, file_transfer.cancellable); file_transfer.path = file.get_basename(); file_transfer.input_stream = yield file.read_async(); @@ -299,14 +299,15 @@ public class FileManager : StreamInteractionModule, Object { if (is_sender_trustworthy(file_transfer, conversation)) { try { yield get_file_meta(file_provider, file_transfer, conversation, receive_data); - - if (file_transfer.size >= 0 && file_transfer.size < 5000000) { - yield download_file_internal(file_provider, file_transfer, conversation); - } } catch (Error e) { warning("Error downloading file: %s", e.message); file_transfer.state = FileTransfer.State.FAILED; } + if (file_transfer.size >= 0 && file_transfer.size < 5000000) { + download_file_internal.begin(file_provider, file_transfer, conversation, (_, res) => { + download_file_internal.end(res); + }); + } } conversation.last_active = file_transfer.time; diff --git a/libdino/tests/testcase.vala b/libdino/tests/testcase.vala index 87147604..59fcf193 100644 --- a/libdino/tests/testcase.vala +++ b/libdino/tests/testcase.vala @@ -48,7 +48,7 @@ public abstract class Gee.TestCase : Object { } public GLib.TestSuite get_suite () { - return this.suite; + return (owned) this.suite; } private class Adaptor { diff --git a/main/CMakeLists.txt b/main/CMakeLists.txt index 63bd4101..1670fb04 100644 --- a/main/CMakeLists.txt +++ b/main/CMakeLists.txt @@ -218,7 +218,7 @@ else() set(DINO_NUMERIC_VERSION "0,0,0,0") endif () -add_definitions(${VALA_CFLAGS} -DGETTEXT_PACKAGE=\"${GETTEXT_PACKAGE}\" -DLOCALE_INSTALL_DIR=\"${LOCALE_INSTALL_DIR}\" -DDINO_VERSION=\"${PROJECT_VERSION}\" -DDINO_NUMERIC_VERSION=${DINO_NUMERIC_VERSION}) +add_definitions(${VALA_CFLAGS} -DGETTEXT_PACKAGE=\"${GETTEXT_PACKAGE}\" -DLOCALE_INSTALL_DIR=\"${LOCALE_INSTALL_DIR}\" -DDINO_NUMERIC_VERSION=${DINO_NUMERIC_VERSION}) if(WIN32) add_link_options("-Wl,--export-all-symbols") set(CMAKE_RC_COMPILE_OBJECT " --use-temp-file -O coff -i -o ") diff --git a/main/data/theme.css b/main/data/theme.css index 3e076248..059585b7 100644 --- a/main/data/theme.css +++ b/main/data/theme.css @@ -369,4 +369,8 @@ box.dino-input-error label.input-status-highlight-once { .dino-call-window .own-video { box-shadow: 0 0 2px 0 rgba(0,0,0,0.5); -} \ No newline at end of file +} + +.qrcode-container { + background: white; /* Color of the quiet zone. MUST have the same "reflectance" as light modules of the QR code. */ +} diff --git a/main/src/ui/application.vala b/main/src/ui/application.vala index 2fd5320e..6c02087f 100644 --- a/main/src/ui/application.vala +++ b/main/src/ui/application.vala @@ -42,7 +42,7 @@ public class Dino.Ui.Application : Gtk.Application, Dino.Application { startup.connect(() => { if (print_version) { - print(@"Dino $(Dino.VERSION)\n"); + print(@"Dino $(Dino.get_version())\n"); Process.exit(0); } @@ -270,7 +270,7 @@ public class Dino.Ui.Application : Gtk.Application, Dino.Application { } private void show_about_window() { - string? version = Dino.VERSION.strip().length == 0 ? null : Dino.VERSION; + string? version = Dino.get_version().strip().length == 0 ? null : Dino.get_version(); if (version != null && !version.contains("git")) { switch (version.substring(0, 3)) { case "0.2": version = @"$version - Mexican Caribbean Coral Reefs"; break; diff --git a/main/src/ui/call_window/call_window_controller.vala b/main/src/ui/call_window/call_window_controller.vala index 65e5ebd6..276fff98 100644 --- a/main/src/ui/call_window/call_window_controller.vala +++ b/main/src/ui/call_window/call_window_controller.vala @@ -22,6 +22,7 @@ public class Dino.Ui.CallWindowController : Object { private bool window_size_changed = false; private ulong[] call_window_handler_ids = new ulong[0]; private ulong[] bottom_bar_handler_ids = new ulong[0]; + private uint inhibit_cookie; public CallWindowController(CallWindow call_window, CallState call_state, StreamInteractor stream_interactor) { this.call_window = call_window; @@ -118,6 +119,19 @@ public class Dino.Ui.CallWindowController : Object { update_audio_device_choices(); update_video_device_choices(); + + var app = GLib.Application.get_default() as Application; + inhibit_cookie = app.inhibit(call_window, IDLE | SUSPEND, "Ongoing call"); + + if (inhibit_cookie == 0) { + warning("suspend inhibit request failed or unsupported"); + } + + call_window.destroy.connect(() => { + if (inhibit_cookie != 0) { + app.uninhibit(inhibit_cookie); + } + }); } private void invite_button_clicked() { diff --git a/main/src/ui/conversation_content_view/file_default_widget.vala b/main/src/ui/conversation_content_view/file_default_widget.vala index 28b7d477..638dab15 100644 --- a/main/src/ui/conversation_content_view/file_default_widget.vala +++ b/main/src/ui/conversation_content_view/file_default_widget.vala @@ -19,6 +19,7 @@ public class FileDefaultWidget : EventBox { public ModelButton file_open_button; public ModelButton file_save_button; + public ModelButton cancel_button; private FileTransfer.State state; @@ -27,6 +28,7 @@ public class FileDefaultWidget : EventBox { this.leave_notify_event.connect(on_pointer_left_event); file_open_button = new ModelButton() { text=_("Open"), visible=true }; file_save_button = new ModelButton() { text=_("Save as…"), visible=true }; + cancel_button = new ModelButton() { text=_("Cancel"), visible=true }; } public void update_file_info(string? mime_type, FileTransfer.State state, long size) { @@ -59,6 +61,18 @@ public class FileDefaultWidget : EventBox { mime_label.label = _("Downloading %s…").printf(get_size_string(size)); spinner.active = true; image_stack.set_visible_child_name("spinner"); + + // Create a menu + Gtk.PopoverMenu popover_menu = new Gtk.PopoverMenu(); + Box file_menu_box = new Box(Orientation.VERTICAL, 0) { margin=10, visible=true }; + file_menu_box.add(cancel_button); + popover_menu.add(file_menu_box); + file_menu.popover = popover_menu; + file_menu.button_release_event.connect(() => { + popover_menu.visible = true; + return true; + }); + popover_menu.closed.connect(on_pointer_left); break; case FileTransfer.State.NOT_STARTED: if (mime_description != null) { @@ -84,7 +98,7 @@ public class FileDefaultWidget : EventBox { if (state == FileTransfer.State.NOT_STARTED) { image_stack.set_visible_child_name("download_image"); } - if (state == FileTransfer.State.COMPLETE) { + if (state == FileTransfer.State.COMPLETE || state == FileTransfer.State.IN_PROGRESS) { file_menu.opacity = 1; } return false; diff --git a/main/src/ui/conversation_content_view/file_widget.vala b/main/src/ui/conversation_content_view/file_widget.vala index 5743e65b..f0353a6f 100644 --- a/main/src/ui/conversation_content_view/file_widget.vala +++ b/main/src/ui/conversation_content_view/file_widget.vala @@ -131,6 +131,7 @@ public class FileDefaultWidgetController : Object { widget.button_release_event.connect(on_clicked); widget.file_open_button.clicked.connect(open_file); widget.file_save_button.clicked.connect(save_file); + widget.cancel_button.clicked.connect(cancel_download); } public void set_file_transfer(FileTransfer file_transfer, StreamInteractor stream_interactor) { @@ -186,6 +187,10 @@ public class FileDefaultWidgetController : Object { } } + private void cancel_download() { + file_transfer.cancellable.cancel(); + } + private bool on_clicked(EventButton event_button) { switch (state) { case FileTransfer.State.COMPLETE: diff --git a/plugins/http-files/CMakeLists.txt b/plugins/http-files/CMakeLists.txt index 77579fdb..2da46731 100644 --- a/plugins/http-files/CMakeLists.txt +++ b/plugins/http-files/CMakeLists.txt @@ -1,10 +1,18 @@ +set(HTTP_FILES_DEFINITIONS) +if(USE_SOUP3) + set(Soup Soup3) + set(HTTP_FILES_DEFINITIONS ${HTTP_FILES_DEFINITIONS} SOUP_3) +else() + set(Soup Soup2) +endif() + find_packages(HTTP_FILES_PACKAGES REQUIRED Gee GLib GModule GObject GTK3 - Soup + ${Soup} ) vala_precompile(HTTP_FILES_VALA_C @@ -19,6 +27,8 @@ CUSTOM_VAPIS ${CMAKE_BINARY_DIR}/exports/qlite.vapi PACKAGES ${HTTP_FILES_PACKAGES} +DEFINITIONS + ${HTTP_FILES_DEFINITIONS} ) add_definitions(${VALA_CFLAGS}) diff --git a/plugins/http-files/src/file_provider.vala b/plugins/http-files/src/file_provider.vala index e3382439..3a3aeb94 100644 --- a/plugins/http-files/src/file_provider.vala +++ b/plugins/http-files/src/file_provider.vala @@ -46,6 +46,38 @@ public class FileProvider : Dino.FileProvider, Object { } } + private class LimitInputStream : InputStream { + InputStream inner; + int64 remaining_size; + + public LimitInputStream(InputStream inner, int64 max_size) { + this.inner = inner; + this.remaining_size = max_size; + } + + private ssize_t check_limit(ssize_t read) throws IOError { + this.remaining_size -= read; + if (remaining_size < 0) throw new IOError.FAILED("Stream length exceeded limit"); + return read; + } + + public override ssize_t read(uint8[] buffer, Cancellable? cancellable = null) throws IOError { + return check_limit(inner.read(buffer, cancellable)); + } + + public override async ssize_t read_async(uint8[]? buffer, int io_priority = GLib.Priority.DEFAULT, Cancellable? cancellable = null) throws IOError { + return check_limit(yield inner.read_async(buffer, io_priority, cancellable)); + } + + public override bool close(Cancellable? cancellable = null) throws IOError { + return inner.close(cancellable); + } + + public override async bool close_async(int io_priority = GLib.Priority.DEFAULT, Cancellable? cancellable = null) throws IOError { + return yield inner.close_async(io_priority, cancellable); + } + } + private void on_file_message(Entities.Message message, Conversation conversation) { var additional_info = message.id.to_string(); @@ -64,24 +96,28 @@ public class FileProvider : Dino.FileProvider, Object { if (http_receive_data == null) return file_meta; var session = new Soup.Session(); + session.user_agent = @"Dino/$(Dino.get_short_version()) "; var head_message = new Soup.Message("HEAD", http_receive_data.url); + head_message.request_headers.append("Accept-Encoding", "identity"); - if (head_message != null) { - try { - yield session.send_async(head_message, null); - } catch (Error e) { - throw new FileReceiveError.GET_METADATA_FAILED("HEAD request failed"); - } + try { +#if SOUP_3 + yield session.send_async(head_message, GLib.Priority.LOW, null); +#else + yield session.send_async(head_message, null); +#endif + } catch (Error e) { + throw new FileReceiveError.GET_METADATA_FAILED("HEAD request failed"); + } - string? content_type = null, content_length = null; - head_message.response_headers.foreach((name, val) => { - if (name == "Content-Type") content_type = val; - if (name == "Content-Length") content_length = val; - }); - file_meta.mime_type = content_type; - if (content_length != null) { - file_meta.size = int.parse(content_length); - } + string? content_type = null, content_length = null; + head_message.response_headers.foreach((name, val) => { + if (name.down() == "content-type") content_type = val; + if (name.down() == "content-length") content_length = val; + }); + file_meta.mime_type = content_type; + if (content_length != null) { + file_meta.size = int64.parse(content_length); } return file_meta; @@ -95,11 +131,21 @@ public class FileProvider : Dino.FileProvider, Object { HttpFileReceiveData? http_receive_data = receive_data as HttpFileReceiveData; if (http_receive_data == null) assert(false); - try { - var session = new Soup.Session(); - Soup.Request request = session.request(http_receive_data.url); + var session = new Soup.Session(); + session.user_agent = @"Dino/$(Dino.get_short_version()) "; + var get_message = new Soup.Message("GET", http_receive_data.url); - return yield request.send_async(null); + try { +#if SOUP_3 + InputStream stream = yield session.send_async(get_message, GLib.Priority.LOW, file_transfer.cancellable); +#else + InputStream stream = yield session.send_async(get_message, file_transfer.cancellable); +#endif + if (file_meta.size != -1) { + return new LimitInputStream(stream, file_meta.size); + } else { + return stream; + } } catch (Error e) { throw new FileReceiveError.DOWNLOAD_FAILED("Downloading file error: %s".printf(e.message)); } diff --git a/plugins/http-files/src/file_sender.vala b/plugins/http-files/src/file_sender.vala index e005b8c5..ab81ffce 100644 --- a/plugins/http-files/src/file_sender.vala +++ b/plugins/http-files/src/file_sender.vala @@ -73,6 +73,7 @@ public class HttpFileSender : FileSender, Object { } } +#if !SOUP_3 private static void transfer_more_bytes(InputStream stream, Soup.MessageBody body) { uint8[] bytes = new uint8[4096]; ssize_t read = stream.read(bytes); @@ -83,25 +84,35 @@ public class HttpFileSender : FileSender, Object { bytes.length = (int)read; body.append_buffer(new Soup.Buffer.take(bytes)); } +#endif private async void upload(FileTransfer file_transfer, HttpFileSendData file_send_data, FileMeta file_meta) throws FileSendError { Xmpp.XmppStream? stream = stream_interactor.get_stream(file_transfer.account); if (stream == null) return; - Soup.Message message = new Soup.Message("PUT", file_send_data.url_up); - message.request_headers.set_content_type(file_meta.mime_type, null); - message.request_headers.set_content_length(file_meta.size); + var session = new Soup.Session(); + session.user_agent = @"Dino/$(Dino.get_short_version()) "; + var put_message = new Soup.Message("PUT", file_send_data.url_up); +#if SOUP_3 + put_message.set_request_body(file_meta.mime_type, file_transfer.input_stream, (ssize_t) file_meta.size); +#else + put_message.request_headers.set_content_type(file_meta.mime_type, null); + put_message.request_headers.set_content_length(file_meta.size); + put_message.request_body.set_accumulate(false); + put_message.wrote_headers.connect(() => transfer_more_bytes(file_transfer.input_stream, put_message.request_body)); + put_message.wrote_chunk.connect(() => transfer_more_bytes(file_transfer.input_stream, put_message.request_body)); +#endif foreach (var entry in file_send_data.headers.entries) { - message.request_headers.append(entry.key, entry.value); + put_message.request_headers.append(entry.key, entry.value); } - message.request_body.set_accumulate(false); - message.wrote_headers.connect(() => transfer_more_bytes(file_transfer.input_stream, message.request_body)); - message.wrote_chunk.connect(() => transfer_more_bytes(file_transfer.input_stream, message.request_body)); - Soup.Session session = new Soup.Session(); try { - yield session.send_async(message); - if (message.status_code < 200 || message.status_code >= 300) { - throw new FileSendError.UPLOAD_FAILED("HTTP status code %s".printf(message.status_code.to_string())); +#if SOUP_3 + yield session.send_async(put_message, GLib.Priority.LOW, file_transfer.cancellable); +#else + yield session.send_async(put_message, file_transfer.cancellable); +#endif + if (put_message.status_code < 200 || put_message.status_code >= 300) { + throw new FileSendError.UPLOAD_FAILED("HTTP status code %s".printf(put_message.status_code.to_string())); } } catch (Error e) { throw new FileSendError.UPLOAD_FAILED("HTTP upload error: %s".printf(e.message)); diff --git a/plugins/omemo/data/contact_details_dialog.ui b/plugins/omemo/data/contact_details_dialog.ui index 188bf06e..62aded6b 100644 --- a/plugins/omemo/data/contact_details_dialog.ui +++ b/plugins/omemo/data/contact_details_dialog.ui @@ -278,7 +278,6 @@ True - 10 True diff --git a/plugins/omemo/src/file_transfer/file_encryptor.vala b/plugins/omemo/src/file_transfer/file_encryptor.vala index 7e79abdc..39af8292 100644 --- a/plugins/omemo/src/file_transfer/file_encryptor.vala +++ b/plugins/omemo/src/file_transfer/file_encryptor.vala @@ -38,7 +38,7 @@ public class OmemoFileEncryptor : Dino.FileEncryptor, Object { omemo_http_file_meta.iv = iv; omemo_http_file_meta.key = key; omemo_http_file_meta.size = file_transfer.size + 16; - omemo_http_file_meta.mime_type = "omemo"; + omemo_http_file_meta.mime_type = "application/octet-stream"; file_transfer.input_stream = new ConverterInputStream(file_transfer.input_stream, new SymmetricCipherEncrypter((owned) cipher, 16)); } catch (Crypto.Error error) { throw new FileSendError.ENCRYPTION_FAILED("OMEMO file encryption error: %s".printf(error.message)); diff --git a/plugins/omemo/src/ui/contact_details_dialog.vala b/plugins/omemo/src/ui/contact_details_dialog.vala index b268cc13..b4d6d8f0 100644 --- a/plugins/omemo/src/ui/contact_details_dialog.vala +++ b/plugins/omemo/src/ui/contact_details_dialog.vala @@ -92,20 +92,23 @@ public class ContactDetailsDialog : Gtk.Dialog { copy_button.clicked.connect(() => {Clipboard.get_default(get_display()).set_text(fingerprint, fingerprint.length);}); int sid = plugin.db.identity.row_with(plugin.db.identity.account_id, account.id)[plugin.db.identity.device_id]; - Pixbuf qr_pixbuf = new QRcode(@"xmpp:$(account.bare_jid)?omemo-sid-$(sid)=$(fingerprint)", 2).to_pixbuf(); - qr_pixbuf = qr_pixbuf.scale_simple(150, 150, InterpType.NEAREST); + var iri_query = @"omemo-sid-$(sid)=$(fingerprint)"; +#if GLIB_2_66 && VALA_0_50 + string iri = GLib.Uri.join(UriFlags.NONE, "xmpp", null, null, 0, jid.to_string(), iri_query, null); +#else + var iri_path_seg = escape_for_iri_path_segment(jid.to_string()); + var iri = @"xmpp:$(iri_path_seg)?$(iri_query)"; +#endif - Pixbuf pixbuf = new Pixbuf( - qr_pixbuf.colorspace, - qr_pixbuf.has_alpha, - qr_pixbuf.bits_per_sample, - 170, - 170 - ); - pixbuf.fill(uint32.MAX); - qr_pixbuf.copy_area(0, 0, 150, 150, pixbuf, 10, 10); + const int QUIET_ZONE_MODULES = 4; // MUST be at least 4 + const int MODULE_SIZE_PX = 4; // arbitrary + var qr_pixbuf = new QRcode(iri, 2) + .to_pixbuf(MODULE_SIZE_PX * qrcode_image.scale_factor); + qrcode_image.set_from_surface( + Gdk.cairo_surface_create_from_pixbuf(qr_pixbuf,0,get_window())); + qrcode_image.margin = QUIET_ZONE_MODULES*MODULE_SIZE_PX; + qrcode_popover.get_style_context().add_class("qrcode-container"); - qrcode_image.set_from_pixbuf(pixbuf); show_qrcode_button.clicked.connect(qrcode_popover.popup); } @@ -130,6 +133,14 @@ public class ContactDetailsDialog : Gtk.Dialog { fetch_unknown_bundles(); } + private static string escape_for_iri_path_segment(string s) { + // from RFC 3986, 2.2. Reserved Characters: + string SUB_DELIMS = "!$&'()*+,;="; + // from RFC 3986, 3.3. Path (pchar without unreserved and pct-encoded): + string ALLOWED_RESERVED_CHARS = SUB_DELIMS + ":@"; + return GLib.Uri.escape_string(s, ALLOWED_RESERVED_CHARS, true); + } + private void fetch_unknown_bundles() { Dino.Application app = Application.get_default() as Dino.Application; XmppStream? stream = app.stream_interactor.get_stream(account); diff --git a/plugins/omemo/vapi/libqrencode.vapi b/plugins/omemo/vapi/libqrencode.vapi index fc77c855..79f98b62 100644 --- a/plugins/omemo/vapi/libqrencode.vapi +++ b/plugins/omemo/vapi/libqrencode.vapi @@ -36,15 +36,47 @@ namespace Qrencode { [CCode (cname = "QRcode_encodeString")] public QRcode (string str, int version = 0, ECLevel level = ECLevel.L, EncodeMode hint = EncodeMode.EIGHT_BIT, bool casesensitive = true); - public Pixbuf to_pixbuf() { - uint8[] bitmap = new uint8[3*width*width]; - for (int i = 0; i < width*width; i++) { - uint8 color = (data[i] & 1) == 1 ? 0 : 255; - bitmap[i*3] = color; - bitmap[i*3+1] = color; - bitmap[i*3+2] = color; + public Pixbuf to_pixbuf(int module_size) { + GLib.assert(module_size > 0); + var dst_width = width*module_size; + var dst_data = new uint8[dst_width*dst_width*3]; + expand_and_upsample(data,width,width, dst_data,dst_width,dst_width); + return new Pixbuf.from_data(dst_data, + Colorspace.RGB, false, 8, dst_width, dst_width, dst_width*3); + } + + /** Does 2D nearest-neighbor upsampling of an array of single-byte + * samples, while expanding the least significant bit of each sample + * to three 0-or-255 bytes. + */ + private void expand_and_upsample( + uint8[] src, uint src_w, uint src_h, + uint8[] dst, uint dst_w, uint dst_h) { + GLib.assert(dst_w % src_w == 0); + GLib.assert(dst_h % src_h == 0); + var scale_x = dst_w/src_w, + scale_y = dst_h/src_h; + /* Doing the iteration in the order of destination samples for + * improved cache-friendliness (dst is 48 times larger than src in + * the typical case of scaling by 4x4). + * The choice of multiple nested loops over a single one is for + * avoiding a ton of divisions by non-constants. + */ + for (uint src_y = 0; src_y < src_h; ++src_y) { + for (uint repeat_y = 0; repeat_y < scale_y; ++repeat_y) { + var dst_y = src_y*scale_y + repeat_y; + for (uint src_x = 0; src_x < src_w; ++src_x) { + uint8 value = (src[src_y*src_w + src_x] & 1)==1 ? 0:255; + for (uint repeat_x = 0; repeat_x < scale_x; ++repeat_x){ + var dst_x = src_x*scale_x + repeat_x; + var dst_idx = dst_y*dst_w + dst_x; + dst[dst_idx*3+0] = value; + dst[dst_idx*3+1] = value; + dst[dst_idx*3+2] = value; + } + } + } } - return new Pixbuf.from_data(bitmap, Colorspace.RGB, false, 8, width, width, width*3); } } } diff --git a/plugins/rtp/src/device.vala b/plugins/rtp/src/device.vala index d4eca09a..1db8c996 100644 --- a/plugins/rtp/src/device.vala +++ b/plugins/rtp/src/device.vala @@ -354,6 +354,7 @@ public class Dino.Plugins.Rtp.Device : MediaDevice, Object { int best_height = 0; for (int i = 0; i < device.caps.get_size(); i++) { unowned Gst.Structure? that = device.caps.get_structure(i); + Value? best_fraction_now = null; if (!that.has_name("video/x-raw")) continue; int num = 0, den = 0, width = 0, height = 0; if (!that.has_field("framerate")) continue; @@ -369,7 +370,7 @@ public class Dino.Plugins.Rtp.Device : MediaDevice, Object { int fps = den > 0 ? (num/den) : 0; int in_fps = in_den > 0 ? (in_num/in_den) : 0; if (in_fps > fps) { - best_fraction = fraction; + best_fraction_now = fraction; num = in_num; den = in_den; } @@ -386,6 +387,7 @@ public class Dino.Plugins.Rtp.Device : MediaDevice, Object { best_width = width; best_height = height; best_index = i; + best_fraction = best_fraction_now; } } Gst.Caps res = caps_copy_nth(device.caps, best_index); diff --git a/plugins/rtp/src/plugin.vala b/plugins/rtp/src/plugin.vala index 232a19e4..219fc55c 100644 --- a/plugins/rtp/src/plugin.vala +++ b/plugins/rtp/src/plugin.vala @@ -384,9 +384,27 @@ public class Dino.Plugins.Rtp.Plugin : RootInterface, VideoCallPlugin, Object { int fps = 0; for (int i = 0; i < device.device.caps.get_size(); i++) { unowned Gst.Structure structure = device.device.caps.get_structure(i); - int num = 0, den = 0; - if (structure.has_field("framerate") && structure.get_fraction("framerate", out num, out den)) fps = int.max(fps, num / den); + + if (structure.has_field("framerate")) { + Value framerate = structure.get_value("framerate"); + if (framerate.type() == typeof(Gst.Fraction)) { + int num = Gst.Value.get_fraction_numerator(framerate); + int den = Gst.Value.get_fraction_denominator(framerate); + fps = int.max(fps, num / den); + } else if (framerate.type() == typeof(Gst.ValueList)) { + for(uint j = 0; j < Gst.ValueList.get_size(framerate); j++) { + Value fraction = Gst.ValueList.get_value(framerate, j); + int num = Gst.Value.get_fraction_numerator(fraction); + int den = Gst.Value.get_fraction_denominator(fraction); + fps = int.max(fps, num / den); + } + } else { + debug("Unknown type for framerate %s on device %s", framerate.type_name(), device.display_name); + } + } } + + debug("Max framerate for device %s: %d", device.display_name, fps); return fps; } diff --git a/plugins/signal-protocol/tests/testcase.vala b/plugins/signal-protocol/tests/testcase.vala index 061ccc63..59fcf193 100644 --- a/plugins/signal-protocol/tests/testcase.vala +++ b/plugins/signal-protocol/tests/testcase.vala @@ -48,7 +48,7 @@ public abstract class Gee.TestCase : Object { } public GLib.TestSuite get_suite () { - return this.suite; + return (owned) this.suite; } private class Adaptor { @@ -77,4 +77,4 @@ public abstract class Gee.TestCase : Object { this.test_case.tear_down (); } } -} \ No newline at end of file +} diff --git a/xmpp-vala/tests/testcase.vala b/xmpp-vala/tests/testcase.vala index 9bdf5f6b..178b6353 100644 --- a/xmpp-vala/tests/testcase.vala +++ b/xmpp-vala/tests/testcase.vala @@ -56,7 +56,7 @@ public abstract class Gee.TestCase : Object { } public GLib.TestSuite get_suite () { - return this.suite; + return (owned) this.suite; } }