From 8d49d83b3b45ab22d7b9d945c4b32296b07cb49e Mon Sep 17 00:00:00 2001 From: fiaxh Date: Fri, 14 Aug 2026 11:45:08 +0200 Subject: Add initial support for multi-file transfers --- main/src/ui/application.vala | 10 ++ main/src/ui/chat_input/chat_input_controller.vala | 8 +- .../content_populator.vala | 2 + .../file_default_widget.vala | 40 ++++--- .../file_group_widget.vala | 120 +++++++++++++++++++++ .../file_image_widget.vala | 105 ++++++++++++------ .../file_transmission_progress.vala | 6 +- .../ui/conversation_content_view/file_widget.vala | 71 +----------- .../conversation_selector_row.vala | 19 ++++ main/src/ui/conversation_view.vala | 21 ++++ main/src/ui/conversation_view_controller.vala | 4 - 11 files changed, 283 insertions(+), 123 deletions(-) create mode 100644 main/src/ui/conversation_content_view/file_group_widget.vala (limited to 'main/src/ui') diff --git a/main/src/ui/application.vala b/main/src/ui/application.vala index 1b4dc049..62495871 100644 --- a/main/src/ui/application.vala +++ b/main/src/ui/application.vala @@ -244,6 +244,16 @@ public class Dino.Ui.Application : Adw.Application, Dino.Application { call_state.reject(); }); add_action(deny_call_action); + + SimpleAction save_file_action = new SimpleAction("file_open_save_dialog", VariantType.INT32); + save_file_action.activate.connect((variant) => { + print("action open save dialog\n"); + int file_transfer_id = variant.get_int32(); + FileTransfer? file_transfer = stream_interactor.get_module(FileTransferStorage.IDENTITY).get_file_by_id(file_transfer_id, null); + + window.conversation_view.open_save_file_dialog(file_transfer); + }); + add_action(save_file_action); } private void show_preferences_window() { diff --git a/main/src/ui/chat_input/chat_input_controller.vala b/main/src/ui/chat_input/chat_input_controller.vala index c93a9e22..afbef637 100644 --- a/main/src/ui/chat_input/chat_input_controller.vala +++ b/main/src/ui/chat_input/chat_input_controller.vala @@ -245,11 +245,11 @@ public class ChatInputController : Object { send_text(); } - foreach (File file in files_to_send) { - stream_interactor.get_module(FileManager.IDENTITY).send_file.begin(file, conversation); + if (!files_to_send.is_empty) { + stream_interactor.get_module(FileManager2.IDENTITY).send_files.begin(files_to_send, conversation); + files_to_send = new ArrayList(); + chat_input.clear_files(); } - files_to_send = new ArrayList(); - chat_input.clear_files(); } private void on_text_input_changed() { diff --git a/main/src/ui/conversation_content_view/content_populator.vala b/main/src/ui/conversation_content_view/content_populator.vala index ef859bde..4d1b8c90 100644 --- a/main/src/ui/conversation_content_view/content_populator.vala +++ b/main/src/ui/conversation_content_view/content_populator.vala @@ -70,6 +70,8 @@ public class ContentProvider : ContentItemCollection, Object { return new FileMetaItem(content_item, stream_interactor); } else if (content_item.type_ == CallItem.TYPE) { return new CallMetaItem(content_item, stream_interactor); + } else if (content_item.type_ == FileGroupItem.TYPE) { + return new FileGroupMetaItem(content_item, stream_interactor); } critical("Got unknown content item type %s", content_item.type_); return null; 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 a21e2afb..1ad3d6c2 100644 --- a/main/src/ui/conversation_content_view/file_default_widget.vala +++ b/main/src/ui/conversation_content_view/file_default_widget.vala @@ -51,13 +51,13 @@ public class FileDefaultWidget : Box { mime_label.label = content_type != null ? content_type.get_description() : null; } - public void update_file_info(Xmpp.FileContentType? content_type, FileTransfer.State state, bool direction, int64 size, int64 transferred_bytes) { - this.state = state; + public void update_file_info(FileTransfer file_transfer) { + this.state = file_transfer.state; spinner.stop(); // A hidden spinning spinner still uses CPU. Deactivate asap - content_type_image.icon_name = get_file_icon_name(content_type); - string? mime_description = content_type != null ? content_type.get_description() : null; + content_type_image.icon_name = get_file_icon_name(file_transfer.content_type); + string? mime_description = file_transfer.content_type != null ? file_transfer.content_type.get_description() : null; switch (state) { case FileTransfer.State.COMPLETE: @@ -66,23 +66,31 @@ public class FileDefaultWidget : Box { // Create a menu Menu menu_model = new Menu(); - menu_model.append(_("Open"), "file.open"); - menu_model.append(_("Save as…"), "file.save_as"); + + MenuItem open_file_item = new MenuItem(_("Open"), "app.file_open_externally"); + open_file_item.set_action_and_target_value("app.file_open_externally", new Variant.int32(file_transfer.id)); + + MenuItem save_as_item = new MenuItem(_("Save as…"), "app.file_save_as"); + save_as_item.set_action_and_target_value("app.file_open_save_dialog", new Variant.int32(file_transfer.id)); + + menu_model.append_item(open_file_item); + menu_model.append_item(save_as_item); + Gtk.PopoverMenu popover_menu = new Gtk.PopoverMenu.from_model(menu_model); file_menu.popover = popover_menu; popover_menu.closed.connect(on_pointer_left); break; case FileTransfer.State.IN_PROGRESS: - if (direction == FileTransfer.DIRECTION_RECEIVED) { - if (size > 0) { - int64 progress = transferred_bytes * 100 / size; - mime_label.label = _("Downloading %s… (%u%%)").printf(get_size_string(size), progress); + if (file_transfer.direction == FileTransfer.DIRECTION_RECEIVED) { + if (file_transfer.size > 0) { + int64 progress = file_transfer.transferred_bytes * 100 / file_transfer.size; + mime_label.label = _("Downloading %s… (%u%%)").printf(get_size_string(file_transfer.size), progress); } else { - mime_label.label = _("Downloading %s…").printf(get_size_string(size)); + mime_label.label = _("Downloading %s…").printf(get_size_string(file_transfer.size)); } } else { - int64 progress = transferred_bytes * 100 / size; - mime_label.label = _("Uploading %s… (%u%%)").printf(get_size_string(size), progress); + int64 progress = file_transfer.transferred_bytes * 100 / file_transfer.size; + mime_label.label = _("Uploading %s… (%u%%)").printf(get_size_string(file_transfer.size), progress); } spinner.start(); image_stack.set_visible_child_name("spinner"); @@ -96,9 +104,9 @@ public class FileDefaultWidget : Box { break; case FileTransfer.State.NOT_STARTED: if (mime_description != null) { - mime_label.label = _("%s offered: %s").printf(mime_description, get_size_string(size)); - } else if (size != -1) { - mime_label.label = _("File offered: %s").printf(get_size_string(size)); + mime_label.label = _("%s offered: %s").printf(mime_description, get_size_string(file_transfer.size)); + } else if (file_transfer.size != -1) { + mime_label.label = _("File offered: %s").printf(get_size_string(file_transfer.size)); } else { mime_label.label = _("File offered"); } diff --git a/main/src/ui/conversation_content_view/file_group_widget.vala b/main/src/ui/conversation_content_view/file_group_widget.vala new file mode 100644 index 00000000..80c41a7a --- /dev/null +++ b/main/src/ui/conversation_content_view/file_group_widget.vala @@ -0,0 +1,120 @@ +using Gee; +using Gdk; +using Gtk; +using Pango; +using Xmpp; + +using Dino.Entities; + +namespace Dino.Ui { + + public class FileGroupMetaItem : ConversationSummary.ContentMetaItem { + + private StreamInteractor stream_interactor; + private FileGroupItem file_group_item; + + public FileGroupMetaItem(ContentItem content_item, StreamInteractor stream_interactor) { + base(content_item); + this.stream_interactor = stream_interactor; + this.file_group_item = content_item as FileGroupItem; + } + + public override Object? get_widget(Plugins.ConversationItemWidgetInterface outer, Plugins.WidgetType type) { + var file_transfers = file_group_item.file_group.file_transfers; + + if (file_transfers.size > 1 && all_files_images(file_transfers)) { + return new ImageGroupWidget(file_transfers); + } else { + Box box = new Box(Orientation.VERTICAL, 6); + foreach (FileTransfer file_transfer in file_transfers) { + FileWidget widget = new FileWidget(file_transfer) { halign = Align.START }; + widget.add_css_class("file-image-widget"); + FileWidgetController widget_controller = new FileWidgetController(widget, file_transfer, stream_interactor); + box.append(widget); + } + return box; + } + } + + public override Gee.List? get_item_actions(Plugins.WidgetType type) { + Gee.List actions = new ArrayList(); + return actions; + } + } + + public class ImageGroupWidget : Box { + + private Gee.ArrayList file_transfers; + + public ImageGroupWidget(Gee.ArrayList file_transfers) { + this.file_transfers = file_transfers; + + if (file_transfers.size == 3) { + Box box = new Box(Orientation.HORIZONTAL, 4) { + overflow = Overflow.HIDDEN, + halign = Align.START + }; + box.add_css_class("image-group"); + + var picture1 = new FileImageWidget.from_file_transfer(file_transfers[0], 200); + box.append(picture1); + + Box box1 = new Box(Orientation.VERTICAL, 4); + + var picture2 = new FileImageWidget.from_file_transfer(file_transfers[1], 100); + box1.append(picture2); + var picture3 = new FileImageWidget.from_file_transfer(file_transfers[2], 100); + box1.append(picture3); + box.append(box1); + + this.append(box); + } else { + Box box = new Box(Orientation.VERTICAL, 4) { + overflow = Overflow.HIDDEN, + halign = Align.START + }; + box.add_css_class("image-group"); + + Box row_upper_row = new Box(Orientation.HORIZONTAL, 4); + + var picture1 = new FileImageWidget.from_file_transfer(file_transfers[0], 150); + row_upper_row.append(picture1); + + var picture2 = new FileImageWidget.from_file_transfer(file_transfers[1], 150); + row_upper_row.append(picture2); + box.append(row_upper_row); + + if (file_transfers.size >= 4) { + + int size = 150; + if (file_transfers.size == 5) size = 99; + + Box row_lower_row = new Box(Orientation.HORIZONTAL, 4); + + var picture3 = new FileImageWidget.from_file_transfer(file_transfers[2], size); + row_lower_row.append(picture3); + + var picture4 = new FileImageWidget.from_file_transfer(file_transfers[3], size); + row_lower_row.append(picture4); + + if (file_transfers.size == 5) { + var picture5 = new FileImageWidget.from_file_transfer(file_transfers[4], size); + row_lower_row.append(picture5); + } + + box.append(row_lower_row); + } + this.append(box); + } + } + } + + public static bool all_files_images(Gee.ArrayList file_transfers) { + foreach (FileTransfer file_transfer in file_transfers) { + if (!Dino.Util.is_pixbuf_supported_content_type(file_transfer.content_type)) { + return false; + } + } + return true; + } +} \ No newline at end of file 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 e936ca25..9f12f892 100644 --- a/main/src/ui/conversation_content_view/file_image_widget.vala +++ b/main/src/ui/conversation_content_view/file_image_widget.vala @@ -22,6 +22,8 @@ public class FileImageWidget : Widget { private Gtk.Box image_overlay_toolbar = new Gtk.Box(Orientation.VERTICAL, 0) { halign=Align.END, valign=Align.START, margin_top=10, margin_start=10, margin_end=10, margin_bottom=10, vexpand=false, visible=false }; private Label file_size_label = new Label(null) { halign=Align.START, valign=Align.END, margin_bottom=4, margin_start=4, visible=false }; + private int size = -1; + private FileTransfer file_transfer; private FileTransmissionProgress transmission_progress = new FileTransmissionProgress() { halign=Align.CENTER, valign=Align.CENTER, visible=false }; @@ -34,22 +36,6 @@ public class FileImageWidget : Widget { public FileImageWidget(int MAX_WIDTH=600, int MAX_HEIGHT=300) { this.halign = Align.START; - this.add_css_class("file-image-widget"); - - // Setup menu button overlay - MenuButton button = new MenuButton(); - button.icon_name = "dino-view-more"; - Menu menu_model = new Menu(); - menu_model.append(_("Open"), "file.open"); - menu_model.append(_("Save as…"), "file.save_as"); - Gtk.PopoverMenu popover_menu = new Gtk.PopoverMenu.from_model(menu_model); - button.popover = popover_menu; - image_overlay_toolbar.append(button); - image_overlay_toolbar.add_css_class("card"); - image_overlay_toolbar.add_css_class("toolbar"); - image_overlay_toolbar.add_css_class("compact-card-toolbar"); - image_overlay_toolbar.set_cursor_from_name("default"); - file_size_label.add_css_class("file-details"); overlay.set_child(stack); @@ -65,12 +51,65 @@ public class FileImageWidget : Widget { gesture_click_controller.button = 1; // listen for left clicks gesture_click_controller.released.connect(on_image_clicked); stack.add_controller(gesture_click_controller); + } + + public FileImageWidget.from_file_transfer(FileTransfer file_transfer, int size = -1) { + this(); + + this.file_transfer = file_transfer; + this.size = size; + + this.file_transfer.bind_property("size", file_size_label, "label", BindingFlags.SYNC_CREATE, file_size_label_transform); + this.file_transfer.bind_property("size", transmission_progress, "file-size", BindingFlags.SYNC_CREATE); + this.file_transfer.bind_property("transferred-bytes", transmission_progress, "transferred-size"); + + setup_signals(transmission_progress, file_transfer); + setup_menu_button(); + + file_transfer.notify["state"].connect(refresh_state); + file_transfer.sources_changed.connect(refresh_state); + refresh_state(); + } + + private static void setup_signals(FileTransmissionProgress transmission_progress, FileTransfer file_transfer) { + var file_transfer_id = file_transfer.id; + + transmission_progress.start_download.connect(() => { + Dino.Application.get_default().activate_action("file_start_download", new Variant.int32(file_transfer_id)); + }); + transmission_progress.cancel_transfer.connect(() => { + Dino.Application.get_default().activate_action("file_cancel_transfer", new Variant.int32(file_transfer_id)); + }); + } + + private void setup_menu_button() { + MenuButton button = new MenuButton(); + button.icon_name = "dino-view-more"; + Menu menu_model = new Menu(); + + MenuItem open_file_item = new MenuItem(_("Open"), "app.file_open_externally"); + open_file_item.set_action_and_target_value("app.file_open_externally", new Variant.int32(file_transfer.id)); + + MenuItem save_as_item = new MenuItem(_("Save as…"), "app.file_save_as"); + save_as_item.set_action_and_target_value("app.file_open_save_dialog", new Variant.int32(file_transfer.id)); + + menu_model.append_item(open_file_item); + menu_model.append_item(save_as_item); + + Gtk.PopoverMenu popover_menu = new Gtk.PopoverMenu.from_model(menu_model); + button.popover = popover_menu; + image_overlay_toolbar.append(button); + image_overlay_toolbar.add_css_class("card"); + image_overlay_toolbar.add_css_class("toolbar"); + image_overlay_toolbar.add_css_class("compact-card-toolbar"); + image_overlay_toolbar.set_cursor_from_name("default"); EventControllerMotion this_motion_events = new EventControllerMotion(); this.add_controller(this_motion_events); this_motion_events.enter.connect((controller, x, y) => { (controller.widget as FileImageWidget).on_motion_event_enter(); }); + attach_on_motion_event_leave(this_motion_events, button); } @@ -88,18 +127,6 @@ public class FileImageWidget : Widget { file_size_label.visible = file_transfer != null && file_transfer.direction == FileTransfer.DIRECTION_RECEIVED && file_transfer.state == FileTransfer.State.NOT_STARTED && !file_transfer.sfs_sources.is_empty; } - public async void set_file_transfer(FileTransfer file_transfer) { - this.file_transfer = file_transfer; - - this.file_transfer.bind_property("size", file_size_label, "label", BindingFlags.SYNC_CREATE, file_size_label_transform); - this.file_transfer.bind_property("size", transmission_progress, "file-size", BindingFlags.SYNC_CREATE); - this.file_transfer.bind_property("transferred-bytes", transmission_progress, "transferred-size"); - - file_transfer.notify["state"].connect(refresh_state); - file_transfer.sources_changed.connect(refresh_state); - refresh_state(); - } - private static bool file_size_label_transform(Binding binding, Value from_value, ref Value to_value) { to_value = FileDefaultWidget.get_size_string((int64) from_value); return true; @@ -153,7 +180,7 @@ public class FileImageWidget : Widget { } public async void load_from_file(File file, string file_name) throws GLib.Error { - FixedRatioPicture image = new FixedRatioPicture() { min_width=100, min_height=100, max_width=600, max_height=300 }; + FixedRatioPicture image = create_picture(); FileInputStream file_stream = null; try { @@ -201,7 +228,7 @@ public class FileImageWidget : Widget { throw new Error(-1, 0, "Error scaling preview image"); } - FixedRatioPicture image = new FixedRatioPicture() { min_width=100, min_height=100, max_width=600, max_height=300 }; + FixedRatioPicture image = create_picture(); image.paintable = Texture.for_pixbuf(pixbuf); stack.add_child(image); stack.set_visible_child(image); @@ -216,12 +243,12 @@ public class FileImageWidget : Widget { if (n_press == 1) { image_overlay_toolbar.visible = !image_overlay_toolbar.visible; } else if (n_press == 2) { - this.activate_action("file.open", null); + Dino.Application.get_default().activate_action("file_open_externally", new Variant.int32(file_transfer.id)); image_overlay_toolbar.visible = false; } break; default: - this.activate_action("file.open", null); + Dino.Application.get_default().activate_action("file_open_externally", new Variant.int32(file_transfer.id)); image_overlay_toolbar.visible = false; break; } @@ -237,6 +264,20 @@ public class FileImageWidget : Widget { (file_transfer.state == FileTransfer.State.COMPLETE || file_transfer.thumbnails.size > 0); } + private FixedRatioPicture create_picture() { + if (size == -1) { + return new FixedRatioPicture() { + min_width=100, max_width=600, + min_height=100, max_height=300 + }; + } else { + return new FixedRatioPicture() { + height_request = size, width_request = size, + content_fit = ContentFit.COVER, halign = Align.START + }; + } + } + public override void dispose() { if (overlay != null && overlay.parent != null) overlay.unparent(); base.dispose(); diff --git a/main/src/ui/conversation_content_view/file_transmission_progress.vala b/main/src/ui/conversation_content_view/file_transmission_progress.vala index e3f33448..43885c1b 100644 --- a/main/src/ui/conversation_content_view/file_transmission_progress.vala +++ b/main/src/ui/conversation_content_view/file_transmission_progress.vala @@ -9,6 +9,9 @@ namespace Dino.Ui { public class FileTransmissionProgress : Adw.Bin { + public signal void start_download(); + public signal void cancel_transfer(); + public enum State { UNKNOWN_SOURCE, DOWNLOAD_NOT_STARTED, @@ -124,11 +127,12 @@ namespace Dino.Ui { break; case DOWNLOAD_NOT_STARTED_FAILED_BEFORE: case DOWNLOAD_NOT_STARTED: + start_download(); this.activate_action("file.download", null); break; case DOWNLOADING: case UPLOADING: - this.activate_action("file.cancel", null); + cancel_transfer(); break; } } diff --git a/main/src/ui/conversation_content_view/file_widget.vala b/main/src/ui/conversation_content_view/file_widget.vala index ecc2d3e9..04185343 100644 --- a/main/src/ui/conversation_content_view/file_widget.vala +++ b/main/src/ui/conversation_content_view/file_widget.vala @@ -57,18 +57,6 @@ public class FileWidget : SizeRequestBin { private FileDefaultWidgetController default_widget_controller; private Widget? content = null; - public signal void open_file(); - public signal void save_file_as(); - public signal void start_download(); - public signal void cancel_download(); - - class construct { - install_action("file.open", null, (widget, action_name) => { ((FileWidget) widget).open_file(); }); - install_action("file.save_as", null, (widget, action_name) => { ((FileWidget) widget).save_file_as(); }); - install_action("file.download", null, (widget, action_name) => { ((FileWidget) widget).start_download(); }); - install_action("file.cancel", null, (widget, action_name) => { ((FileWidget) widget).cancel_download(); }); - } - construct { margin_top = 4; size_request_mode = SizeRequestMode.HEIGHT_FOR_WIDTH; @@ -79,11 +67,6 @@ public class FileWidget : SizeRequestBin { this.file_transfer = file_transfer; update_widget.begin(); -// size_allocate.connect((allocation) => { -// if (allocation.height > parent.get_allocated_height()) { -// Idle.add(() => { parent.queue_resize(); return false; }); -// } -// }); file_transfer.bind_property("state", this, "file-transfer-state"); file_transfer.bind_property("content-type", this, "file-transfer-content-type"); @@ -100,8 +83,8 @@ public class FileWidget : SizeRequestBin { FileImageWidget file_image_widget = null; try { - file_image_widget = new FileImageWidget(); - yield file_image_widget.set_file_transfer(file_transfer); + file_image_widget = new FileImageWidget.from_file_transfer(file_transfer); + file_image_widget.add_css_class("file-image-widget"); // If the widget changed in the meanwhile, stop if (content != content_bak) return; @@ -152,50 +135,6 @@ public class FileWidgetController : Object { }); this.file_transfer = file_transfer; this.stream_interactor = stream_interactor; - - widget.open_file.connect(open_file); - widget.save_file_as.connect(save_file); - widget.start_download.connect(start_download); - widget.cancel_download.connect(cancel_download); - } - - private void open_file() { - try{ - AppInfo.launch_default_for_uri(file_transfer.get_file().get_uri(), null); - } catch (Error err) { - warning("Failed to open %s - %s", file_transfer.get_file().get_uri(), err.message); - } - } - - private void save_file() { - var save_dialog = new FileChooserNative(_("Save as…"), widget.get_root() as Gtk.Window, FileChooserAction.SAVE, null, null); - save_dialog.set_modal(true); - save_dialog.set_current_name(file_transfer.file_name); - - save_dialog.response.connect(() => { - File? target_file = save_dialog.get_file(); - if (target_file == null) { - warning("No file returned from save dialog."); - return; - } - try { - file_transfer.get_file().copy(save_dialog.get_file(), GLib.FileCopyFlags.OVERWRITE, null); - } catch (Error err) { - warning("Failed copy file %s - %s", file_transfer.get_file().get_uri(), err.message); - } - }); - - save_dialog.show(); - } - - private void start_download() { - if (stream_interactor != null) { - stream_interactor.get_module(FileManager.IDENTITY).download_file.begin(file_transfer); - } - } - - private void cancel_download() { - file_transfer.cancellable.cancel(); } } @@ -234,16 +173,16 @@ public class FileDefaultWidgetController : Object { private void update_file_info() { state = file_transfer.state; - widget.update_file_info(file_transfer.content_type, file_transfer.state, file_transfer.direction, file_transfer.size, file_transfer.transferred_bytes); + widget.update_file_info(file_transfer); } private void on_clicked() { switch (state) { case FileTransfer.State.COMPLETE: - widget.activate_action("file.open", null); + Dino.Application.get_default().activate_action("file_open_externally", new GLib.Variant.int32(file_transfer.id)); break; case FileTransfer.State.NOT_STARTED: - widget.activate_action("file.download", null); + Dino.Application.get_default().activate_action("file_start_download", new GLib.Variant.int32(file_transfer.id)); break; default: // Clicking doesn't do anything in FAILED and IN_PROGRESS states diff --git a/main/src/ui/conversation_selector/conversation_selector_row.vala b/main/src/ui/conversation_selector/conversation_selector_row.vala index fd5814fa..0e565c84 100644 --- a/main/src/ui/conversation_selector/conversation_selector_row.vala +++ b/main/src/ui/conversation_selector/conversation_selector_row.vala @@ -226,6 +226,25 @@ public class ConversationSelectorRow : ListBoxRow { message_label.label = (file_is_image ? _("Image received") : _("File received") ); } break; + case FileGroupItem.TYPE: + FileGroupItem file_group_item = last_content_item as FileGroupItem; + FileTransfer first_transfer = file_group_item.file_group.file_transfers.get(0); + + if (conversation.type_ == Conversation.Type.GROUPCHAT) { + // TODO properly display nick for oneself + nick_label.label = Util.get_participant_display_name(stream_interactor, conversation, first_transfer.from, true) + ": "; + } else { + nick_label.label = first_transfer.direction == Message.DIRECTION_SENT ? _("Me") + ": " : ""; + } + + bool all_files_images = Ui.all_files_images(file_group_item.file_group.file_transfers); + change_label_attribute(message_label, attr_style_new(Pango.Style.ITALIC)); + if (first_transfer.direction == Message.DIRECTION_SENT) { + message_label.label = (all_files_images ? _("Images sent") : _("Files sent") ); + } else { + message_label.label = (all_files_images ? _("Images received") : _("Files received") ); + } + break; case CallItem.TYPE: CallItem call_item = (CallItem) last_content_item; Call call = call_item.call; diff --git a/main/src/ui/conversation_view.vala b/main/src/ui/conversation_view.vala index 26043666..4bb1f537 100644 --- a/main/src/ui/conversation_view.vala +++ b/main/src/ui/conversation_view.vala @@ -17,6 +17,27 @@ public class ConversationView : Widget { construct { this.layout_manager = new BinLayout(); } + + public void open_save_file_dialog(FileTransfer file_transfer) { + var save_dialog = new FileChooserNative(_("Save as…"), (Gtk.Window) this.get_root(), FileChooserAction.SAVE, null, null); + save_dialog.set_modal(true); + save_dialog.set_current_name(file_transfer.file_name); + + save_dialog.response.connect(() => { + File? target_file = save_dialog.get_file(); + if (target_file == null) { + warning("No file returned from save dialog."); + return; + } + try { + file_transfer.get_file().copy(save_dialog.get_file(), GLib.FileCopyFlags.OVERWRITE, null); + } catch (Error err) { + warning("Failed copy file %s - %s", file_transfer.get_file().get_uri(), err.message); + } + }); + + save_dialog.show(); + } } } diff --git a/main/src/ui/conversation_view_controller.vala b/main/src/ui/conversation_view_controller.vala index ef13b026..9626a39d 100644 --- a/main/src/ui/conversation_view_controller.vala +++ b/main/src/ui/conversation_view_controller.vala @@ -216,10 +216,6 @@ public class ConversationViewController : Object { chooser.show(); } - private void send_file(File file) { - stream_interactor.get_module(FileManager.IDENTITY).send_file.begin(file, conversation); - } - private bool forward_key_press_to_chat_input(EventControllerKey key_controller, uint keyval, uint keycode, Gdk.ModifierType state) { if (view.get_root().get_focus() is TextView) { return false; -- cgit v1.3 From 0b7283c64b7ba2038a01005110e52ee6f769cbc6 Mon Sep 17 00:00:00 2001 From: Marvin W Date: Sat, 22 Aug 2026 12:27:17 +0200 Subject: Handle roster action on xmpp:-uri --- main/src/ui/application.vala | 1 + 1 file changed, 1 insertion(+) (limited to 'main/src/ui') diff --git a/main/src/ui/application.vala b/main/src/ui/application.vala index 62495871..83290743 100644 --- a/main/src/ui/application.vala +++ b/main/src/ui/application.vala @@ -85,6 +85,7 @@ public class Dino.Ui.Application : Adw.Application, Dino.Application { case "join": show_join_muc_dialog(null, jid); break; + case "roster": case "message": Gee.List accounts = stream_interactor.get_accounts(); Jid parsed_jid = null; -- cgit v1.3 From c71601077699df8464fce4cf9dc11bea598c071e Mon Sep 17 00:00:00 2001 From: Marvin W Date: Sat, 22 Aug 2026 16:21:13 +0200 Subject: Use meson native project version --- build-aux/version.py | 80 ++++++++++++++++++++++++++++++++++++++++++++ libdino/meson.build | 17 ++-------- libdino/src/application.vala | 8 +++-- libdino/src/version.c.in | 4 +++ libdino/src/version.vala.in | 5 --- libdino/version.py | 65 ----------------------------------- main/src/ui/main_window.vala | 3 ++ meson.build | 45 +++++++++++++++++-------- 8 files changed, 125 insertions(+), 102 deletions(-) create mode 100644 build-aux/version.py create mode 100644 libdino/src/version.c.in delete mode 100644 libdino/src/version.vala.in delete mode 100644 libdino/version.py (limited to 'main/src/ui') diff --git a/build-aux/version.py b/build-aux/version.py new file mode 100644 index 00000000..45fdfcd3 --- /dev/null +++ b/build-aux/version.py @@ -0,0 +1,80 @@ +import argparse +import subprocess +import re + + +def compute_version_from_file(file): + try: + with open(file) as f: + version_from_file = f.read().strip() + if version_from_file != "": + if version_from_file.startswith("RELEASE "): + return version_from_file[8:].strip() + if version_from_file.startswith("PRERELEASE "): + return version_from_file[11:].strip() + return version_from_file + except FileNotFoundError: + pass + return None + + +def compute_version_from_git(git_repo, git): + try: + git_release_tag = subprocess.check_output([git, "describe", "--tags", "--abbrev=0"], + cwd=git_repo, text=True).strip() + if re.match("^v?([0-9]+[.]?[0-9]*[.]?[0-9]*)(-[.0-9A-Za-z-]+)?([+][.0-9A-Za-z-]+)?$", git_release_tag) is None: + return None + git_describe = subprocess.check_output([git, "describe", "--tags"], cwd=git_repo, text=True).strip() + if git_release_tag == git_describe: + return git_release_tag[1:] + matches = re.match("^.*-([0-9]+)-g([0-9a-f]+)$", git_describe) + if matches is None: + return None + git_tag_offset = matches.groups()[0] + git_commit_hash = matches.groups()[1] + git_commit_time = subprocess.check_output([git, "show", "--format=%cd", "--date=format:%Y%m%d", "-s"], + cwd=git_repo, text=True).strip() + return "%s~git%s.%s.%s" % (git_release_tag[1:], git_tag_offset, git_commit_time, git_commit_hash) + except subprocess.CalledProcessError: + pass + return None + + +def compute_version(file, git_repo, git): + version_from_file = compute_version_from_file(file) + if version_from_file is not None: + return version_from_file + version_from_git = compute_version_from_git(git_repo, git) + if version_from_git is not None: + return version_from_git + return "" + + +def main(): + p = argparse.ArgumentParser(description="Compute the Dino version") + p.add_argument("--git-repo", help="Path to checked out git repository") + p.add_argument("--git", help="Path to git executable", default="git") + p.add_argument("--depfile", help="Create depfile") + p.add_argument("--deptarget", help="Target in depfile") + p.add_argument("version_file", metavar="VERSION_FILE", + help="Use this file's contents as version if the file exists") + args, unknown = p.parse_known_args() + if args.depfile: + with open(args.depfile, mode='w') as f: + f.write("version.stamp: ") + f.write(args.git_repo + "/VERSION ") + f.write(args.git_repo + "/.git/HEAD ") + try: + with open(args.git_repo + "/.git/HEAD") as f2: + head = f2.read().strip() + if head.startswith("ref:"): + f.write(args.git_repo + "/.git/" + head[4:].strip()) + except FileNotFoundError: + pass + else: + version = compute_version(args.version_file, args.git_repo, args.git) + print(version) + + +if __name__ == "__main__": + main() diff --git a/libdino/meson.build b/libdino/meson.build index 12665b18..d0bb2cef 100644 --- a/libdino/meson.build +++ b/libdino/meson.build @@ -1,17 +1,4 @@ -# version_vala -version_py = files('version.py')[0] -if meson.version().version_compare('>=1.4.0') - version_py_path = version_py.full_path() -else - version_py_path = meson.current_source_dir() / 'version.py' -endif -command = [prog_python, version_py_path, version_file_path, '--git-repo', meson.current_source_dir()] -if prog_git.found() - command += ['--git', prog_git] -endif -# We add those dummy args to ensure meson sees the dependency -command += [version_py, version_file] -version_vala = vcs_tag(command: command, input: 'src/version.vala.in', output: 'version.vala', replace_string: '%VERSION%') +version_c = configure_file(input: 'src/version.c.in', output: 'version.c', configuration: {'VERSION': meson.project_version()}) # libdino dependencies = [ @@ -86,7 +73,7 @@ sources = files( 'src/util/util.vala', 'src/util/weak_timeout.vala', ) -sources += [version_vala] +sources += [version_c] c_args = [ '-DDINO_SYSTEM_LIBDIR_NAME="@0@"'.format(get_option('prefix') / get_option('libdir')), '-DDINO_SYSTEM_PLUGIN_DIR="@0@"'.format(get_option('prefix') / get_option('libdir') / get_option('plugindir')), diff --git a/libdino/src/application.vala b/libdino/src/application.vala index 35b706a4..f2e3cc9b 100644 --- a/libdino/src/application.vala +++ b/libdino/src/application.vala @@ -2,10 +2,12 @@ using Dino.Entities; namespace Dino { -public string get_version() { return VERSION; } +public extern unowned string get_version(); + public string get_short_version() { - if (!VERSION.contains("~")) return VERSION; - return VERSION.split("~")[0] + "+"; + string version = get_version(); + if (!version.contains("~")) return version; + return version.split("~")[0] + "+"; } public interface Application : GLib.Application { diff --git a/libdino/src/version.c.in b/libdino/src/version.c.in new file mode 100644 index 00000000..44603c04 --- /dev/null +++ b/libdino/src/version.c.in @@ -0,0 +1,4 @@ +const char* dino_get_version () +{ + return "@VERSION@"; +} \ No newline at end of file diff --git a/libdino/src/version.vala.in b/libdino/src/version.vala.in deleted file mode 100644 index 2711d4a6..00000000 --- a/libdino/src/version.vala.in +++ /dev/null @@ -1,5 +0,0 @@ -namespace Dino { - -public const string VERSION = "%VERSION%"; - -} diff --git a/libdino/version.py b/libdino/version.py deleted file mode 100644 index 87490d18..00000000 --- a/libdino/version.py +++ /dev/null @@ -1,65 +0,0 @@ -import argparse -import subprocess -import re - - -def compute_version_from_file(file): - try: - with open(file) as f: - version_from_file = f.read().strip() - if version_from_file != "": - if version_from_file.startswith("RELEASE "): - return version_from_file[8:].strip() - if version_from_file.startswith("PRERELEASE "): - return version_from_file[11:].strip() - return version_from_file - except FileNotFoundError: - pass - return None - - -def compute_version_from_git(git_repo, git): - try: - git_release_tag = subprocess.check_output([git, "describe", "--tags", "--abbrev=0"], - cwd=git_repo, text=True).strip() - if re.match("^v?([0-9]+[.]?[0-9]*[.]?[0-9]*)(-[.0-9A-Za-z-]+)?([+][.0-9A-Za-z-]+)?$", git_release_tag) is None: - return None - git_describe = subprocess.check_output([git, "describe", "--tags"], cwd=git_repo, text=True).strip() - if git_release_tag == git_describe: - return git_release_tag[1:] - matches = re.match("^.*-([0-9]+)-g([0-9a-f]+)$", git_describe) - if matches is None: - return None - git_tag_offset = matches.groups()[0] - git_commit_hash = matches.groups()[1] - git_commit_time = subprocess.check_output([git, "show", "--format=%cd", "--date=format:%Y%m%d", "-s"], - cwd=git_repo, text=True).strip() - return "%s~git%s.%s.%s" % (git_release_tag[1:], git_tag_offset, git_commit_time, git_commit_hash) - except subprocess.CalledProcessError: - pass - return None - - -def compute_version(file, git_repo, git): - version_from_file = compute_version_from_file(file) - if version_from_file is not None: - return version_from_file - version_from_git = compute_version_from_git(git_repo, git) - if version_from_git is not None: - return version_from_git - return "" - - -def main(): - p = argparse.ArgumentParser(description="Compute the Dino version") - p.add_argument("--git-repo", help="Path to checked out git repository") - p.add_argument("--git", help="Path to git executable", default="git") - p.add_argument("version_file", metavar="VERSION_FILE", - help="Use this file's contents as version if the file exists") - args, unknown = p.parse_known_args() - version = compute_version(args.version_file, args.git_repo, args.git) - print(version) - - -if __name__ == "__main__": - main() diff --git a/main/src/ui/main_window.vala b/main/src/ui/main_window.vala index 6cabfea7..d2a23954 100644 --- a/main/src/ui/main_window.vala +++ b/main/src/ui/main_window.vala @@ -52,6 +52,9 @@ public class MainWindow : Adw.ApplicationWindow { this.title = "Dino"; this.add_css_class("dino-main"); + if (Dino.get_version().contains("~git")) { + this.add_css_class("devel"); + } ((Widget)this).realize.connect(restore_window_size); diff --git a/meson.build b/meson.build index eb372b6a..0fbe797d 100644 --- a/meson.build +++ b/meson.build @@ -1,9 +1,37 @@ -project('Dino', 'c', 'cpp', 'vala', license: 'GPL-3.0', meson_version: '>=1.3.0') +project( + 'Dino', + 'c', 'cpp', 'vala', + license: 'GPL-3.0', + meson_version: '>=1.3.0', + version: run_command( + find_program('python3', required: true), # Can't use import('python').find_installation() here + meson.current_source_dir() / 'build-aux' / 'version.py', + meson.current_source_dir() / 'VERSION', + '--git-repo', meson.current_source_dir(), + '--git', find_program('git', required: false), + check: true + ).stdout().strip() +) -fs = import('fs') gnome = import('gnome') i18n = import('i18n') -python = import('python') + +# Trigger recomnfiguration on version change +version_stamp = configure_file( + command: [ + # Keep in sync with above + find_program('python3', required: true), + meson.current_source_dir() / 'build-aux' / 'version.py', + meson.current_source_dir() / 'VERSION', + '--git-repo', meson.current_source_dir(), + '--git', find_program('git', required: false), + # Trigger depfile generation + '--depfile', '@DEPFILE@' + ], + input: 'build-aux' / 'version.py', + output: 'version.stamp', + depfile: 'version.d' +) # 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 @@ -53,17 +81,6 @@ if not dep_webrtc_audio_processing.found() dep_webrtc_audio_processing = disabler() endif -prog_git = find_program('git', required: false) -prog_python = python.find_installation() - -if import('fs').is_file('VERSION') and meson.version().version_compare('>=1.4.0') - version_file = files('VERSION')[0] - version_file_path = version_file.full_path() -else - version_file = 'invalid' - version_file_path = meson.current_source_dir() / 'VERSION' -endif - default_install_rpath = '' if get_option('set-install-rpath') default_install_rpath = get_option('prefix') / get_option('libdir') -- cgit v1.3 From 2413442905e31f34cb0f77c9167003b3725760fe Mon Sep 17 00:00:00 2001 From: Marvin W Date: Sat, 3 Oct 2026 16:05:51 +0200 Subject: Drop supported for outdated dependency versions --- libdino/meson.build | 3 -- libdino/src/service/stream_interactor.vala | 5 --- main/meson.build | 29 +-------------- .../file_transmission_progress.vala | 13 ------- main/src/ui/util/helper.vala | 11 ------ main/src/ui/widgets/avatar_picture.vala | 4 -- main/src/ui/widgets/fixed_ratio_picture.vala | 2 - main/src/windows/conversation_details.vala | 4 -- meson.build | 29 ++++++++------- .../omemo/src/ui/encryption_preferences_entry.vala | 5 --- plugins/openpgp/meson.build | 3 -- plugins/rtp/meson.build | 9 ----- plugins/rtp/src/codec_util.vala | 2 - plugins/rtp/src/plugin.vala | 2 - plugins/rtp/src/stream.vala | 43 +++------------------- plugins/rtp/src/video_widget.vala | 15 -------- xmpp-vala/meson.build | 3 -- xmpp-vala/src/core/direct_tls_xmpp_stream.vala | 2 - xmpp-vala/src/core/module_flag.vala | 10 ----- 19 files changed, 22 insertions(+), 172 deletions(-) (limited to 'main/src/ui') diff --git a/libdino/meson.build b/libdino/meson.build index e680b662..a3430200 100644 --- a/libdino/meson.build +++ b/libdino/meson.build @@ -119,9 +119,6 @@ c_args = vala_c_args + [ vala_args = [ '--vapidir', meson.current_source_dir() / 'vapi', ] -if meson.get_compiler('vala').version().version_compare('=0.56.11') - vala_args += ['-D', 'VALA_0_56_11'] -endif lib_dino = library('libdino', sources, c_args: c_args, vala_args: vala_args, include_directories: include_directories('src'), dependencies: dependencies, name_prefix: '', version: '0.0', install: true, install_dir: [true, true, true], install_rpath: default_install_rpath) dep_dino = declare_dependency(link_with: lib_dino, include_directories: include_directories('.', 'src')) diff --git a/libdino/src/service/stream_interactor.vala b/libdino/src/service/stream_interactor.vala index 2ac13103..2cd0c45c 100644 --- a/libdino/src/service/stream_interactor.vala +++ b/libdino/src/service/stream_interactor.vala @@ -100,12 +100,7 @@ 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/meson.build b/main/meson.build index 3c15db32..fbe78573 100644 --- a/main/meson.build +++ b/main/meson.build @@ -47,6 +47,7 @@ sources = files( 'src/ui/conversation_content_view/date_separator_populator.vala', 'src/ui/conversation_content_view/file_default_widget.vala', 'src/ui/conversation_content_view/file_image_widget.vala', + 'src/ui/conversation_content_view/file_transmission_progress.c', 'src/ui/conversation_content_view/file_transmission_progress.vala', 'src/ui/conversation_content_view/file_group_widget.vala', 'src/ui/conversation_content_view/file_widget.vala', @@ -113,34 +114,6 @@ c_args = vala_c_args + [ vala_args = [ '--vapidir', meson.current_source_dir() / 'vapi', ] -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 -if dep_gtk4.version() == 'unknown' or dep_gtk4.version().version_compare('>=4.12') - vala_args += ['-D', 'GTK_4_12'] -endif -if dep_gtk4.version() == 'unknown' or dep_gtk4.version().version_compare('>=4.14') - vala_args += ['-D', 'GTK_4_14'] - sources += files('src/ui/conversation_content_view/file_transmission_progress.c') -endif -if dep_libadwaita.version() == 'unknown' or dep_libadwaita.version().version_compare('>=1.4') - vala_args += ['-D', 'Adw_1_4'] -endif -if meson.get_compiler('vala').version().version_compare('>=0.56.5') and meson.get_compiler('vala').version().version_compare('<0.58') - vala_args += ['-D', 'VALA_0_56_GREATER_5'] -endif -if meson.get_compiler('vala').version().version_compare('>=0.56.11') and meson.get_compiler('vala').version().version_compare('<0.58') - vala_args += ['-D', 'VALA_0_56_GREATER_11'] -endif -if meson.get_compiler('vala').version().version_compare('=0.56.11') - vala_args += ['-D', 'VALA_0_56_11'] -endif -if meson.get_compiler('vala').version().version_compare('=0.56.12') - vala_args += ['-D', 'VALA_0_56_12'] -endif exe_dino = executable('dino', sources, c_args: c_args, vala_args: vala_args, dependencies: dependencies, install: true, install_rpath: default_install_rpath) install_data('data/icons/scalable/apps/im.dino.Dino-symbolic.svg', install_dir: get_option('datadir') / 'icons/hicolor/symbolic/apps') diff --git a/main/src/ui/conversation_content_view/file_transmission_progress.vala b/main/src/ui/conversation_content_view/file_transmission_progress.vala index 43885c1b..3d893f52 100644 --- a/main/src/ui/conversation_content_view/file_transmission_progress.vala +++ b/main/src/ui/conversation_content_view/file_transmission_progress.vala @@ -84,9 +84,7 @@ namespace Dino.Ui { } } -#if GTK_4_14 private static extern Gsk.Path create_progress_arc(Gsk.Path circle, float percentage); -#endif public override void snapshot(Gtk.Snapshot snapshot) { base.snapshot(snapshot); @@ -97,7 +95,6 @@ namespace Dino.Ui { float line_width = (float) LINE_WIDTH; snapshot.translate({get_width() / 2, get_height() / 2}); -#if GTK_4_14 var fg_color = get_color(); get_style_context().lookup_color("accent_color", out fg_color); var builder = new Gsk.PathBuilder(); @@ -107,16 +104,6 @@ namespace Dino.Ui { stroke.set_line_cap(Gsk.LineCap.ROUND); var arc_path = create_progress_arc(circle_path, (float) progress_animation.value); snapshot.append_stroke(arc_path, stroke, fg_color); -#else - var fg_color = get_style_context().get_color(); - get_style_context().lookup_color("accent_color", out fg_color); - var context = snapshot.append_cairo({{-radius, -radius}, {radius*2, radius*2}}); - context.set_line_cap(Cairo.LineCap.ROUND); - context.set_line_width(line_width); - context.set_source_rgba(fg_color.red, fg_color.green, fg_color.blue, fg_color.alpha); - context.arc(0, 0, radius - line_width / 2, -0.5 * Math.PI, (progress_animation.value - 0.25) * 2 * Math.PI); - context.stroke(); -#endif } } diff --git a/main/src/ui/util/helper.vala b/main/src/ui/util/helper.vala index e9035be0..a126a196 100644 --- a/main/src/ui/util/helper.vala +++ b/main/src/ui/util/helper.vala @@ -415,18 +415,7 @@ public bool use_tooltips() { } public static void menu_button_set_icon_with_size(MenuButton menu_button, string icon_name, int pixel_size) { -#if GTK_4_6 && VALA_0_52 menu_button.set_child(new Image.from_icon_name(icon_name) { pixel_size=pixel_size }); -#else - menu_button.set_icon_name(icon_name); - var button = menu_button.get_first_child() as Button; - if (button == null) return; - var box = button.child as Box; - if (box == null) return; - var image = box.get_first_child() as Image; - if (image == null) return; - image.pixel_size = pixel_size; -#endif } } diff --git a/main/src/ui/widgets/avatar_picture.vala b/main/src/ui/widgets/avatar_picture.vala index 155c9440..12c635aa 100644 --- a/main/src/ui/widgets/avatar_picture.vala +++ b/main/src/ui/widgets/avatar_picture.vala @@ -455,11 +455,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_56_GREATER_5 || VALA_0_58) picture.content_fit = Gtk.ContentFit.COVER; -#elif GTK_4_8 - picture.@set("content-fit", 2); -#endif picture.insert_after(this, label); this.notify["model"].connect(on_model_changed); check_widget_leak(this); diff --git a/main/src/ui/widgets/fixed_ratio_picture.vala b/main/src/ui/widgets/fixed_ratio_picture.vala index 6b04ea61..1898efa7 100644 --- a/main/src/ui/widgets/fixed_ratio_picture.vala +++ b/main/src/ui/widgets/fixed_ratio_picture.vala @@ -8,9 +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_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(); construct { diff --git a/main/src/windows/conversation_details.vala b/main/src/windows/conversation_details.vala index e1f3061f..8ec3125b 100644 --- a/main/src/windows/conversation_details.vala +++ b/main/src/windows/conversation_details.vala @@ -160,11 +160,7 @@ namespace Dino.Ui.ConversationDetails { } private void create_members() { -#if GTK_4_8 && (VALA_0_56_GREATER_5 || VALA_0_58) if (model.members_sorted.n_items == 0) return; -#else - if (model.members_sorted.model.get_n_items() == 0) return; -#endif var selection_model = new NoSelection(model.members_sorted); var item_factory = new BuilderListItemFactory.from_resource(null, "/im/dino/Dino/muc_member_list_row.ui"); diff --git a/meson.build b/meson.build index ad0f7e4e..7752f7d0 100644 --- a/meson.build +++ b/meson.build @@ -17,6 +17,12 @@ gnome = import('gnome') i18n = import('i18n') cc = meson.get_compiler('c') +vala_version_required = '0.56.14' +vala = meson.get_compiler('vala') +if not vala.version().version_compare('>= @0@'.format(vala_version_required)) + error('valac >= @0@ required!'.format(vala_version_required)) +endif + # Trigger recomnfiguration on version change version_stamp = configure_file( command: [ @@ -74,23 +80,23 @@ 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_glib = dependency('glib-2.0', version: '>=2.66') 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_gstreamer = dependency('gstreamer-1.0', version: '>=1.20') +dep_gstreamer_app = dependency('gstreamer-app-1.0', version: '>=1.20') +dep_gstreamer_audio = dependency('gstreamer-audio-1.0', version: '>=1.20') +dep_gstreamer_rtp = dependency('gstreamer-rtp-1.0', version: '>=1.20') +dep_gstreamer_video = dependency('gstreamer-video-1.0', version: '>=1.20') +dep_gtk4 = dependency('gtk4', version: '>=4.14') dep_icu_uc = dependency('icu-uc') dep_libadwaita = dependency('libadwaita-1', version: '>=1.5') dep_libcanberra = dependency('libcanberra', disabler: true, required: get_option('plugin-notification-sound')) dep_libqrencode = dependency('libqrencode', disabler: true, required: get_option('plugin-omemo')) -dep_libsrtp2 = dependency('libsrtp2', disabler: true, required: true) +dep_libsrtp2 = dependency('libsrtp2') dep_libomemo_c = dependency('libomemo-c', disabler: true, required: get_option('plugin-omemo')) dep_libsoup = dependency('libsoup-3.0') -dep_nice = dependency('nice', version: '>=0.1.15', disabler: true, required: get_option('plugin-ice')) +dep_nice = dependency('nice', version: '>=0.1.15') dep_m = meson.get_compiler('c').find_library('m', required: false) dep_sqlite3 = dependency('sqlite3', version: '>=3.24') @@ -99,10 +105,7 @@ if not dep_webrtc_audio_processing.found() dep_webrtc_audio_processing = dependency('webrtc-audio-processing', version: ['>=0.2'], required: false) endif if not dep_webrtc_audio_processing.found() - dep_webrtc_audio_processing = dependency('webrtc-audio-processing-2', required: false) -endif -if not dep_webrtc_audio_processing.found() - dep_webrtc_audio_processing = disabler() + dep_webrtc_audio_processing = dependency('webrtc-audio-processing-2', required: false, disabler: true) endif default_install_rpath = '' diff --git a/plugins/omemo/src/ui/encryption_preferences_entry.vala b/plugins/omemo/src/ui/encryption_preferences_entry.vala index cea856bb..cb14978b 100644 --- a/plugins/omemo/src/ui/encryption_preferences_entry.vala +++ b/plugins/omemo/src/ui/encryption_preferences_entry.vala @@ -166,12 +166,7 @@ public class OmemoPreferencesWidget : Adw.PreferencesGroup { // Create and set QR code popover int sid = plugin.db.identity.row_with(plugin.db.identity.account_id, account.id)[plugin.db.identity.device_id]; 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 const int QUIET_ZONE_MODULES = 4; // MUST be at least 4 const int MODULE_SIZE_PX = 4; // arbitrary diff --git a/plugins/openpgp/meson.build b/plugins/openpgp/meson.build index 56bc9d0c..78641012 100644 --- a/plugins/openpgp/meson.build +++ b/plugins/openpgp/meson.build @@ -38,9 +38,6 @@ c_args = vala_c_args + [ vala_args = [ '--vapidir', meson.current_source_dir() / 'vapi', ] -if dep_libadwaita.version() == 'unknown' or dep_libadwaita.version().version_compare('>=1.4') - vala_args += ['-D', 'Adw_1_4'] -endif 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') / get_option('plugindir'), install_rpath: default_install_rpath) dep_openpgp = declare_dependency(link_with: lib_openpgp, include_directories: include_directories('.')) summary('End-to-end encryption using PGP (openpgp)', dep_openpgp.found(), bool_yn: true, section: 'Plugins') diff --git a/plugins/rtp/meson.build b/plugins/rtp/meson.build index 2772ad88..f10fbc25 100644 --- a/plugins/rtp/meson.build +++ b/plugins/rtp/meson.build @@ -52,15 +52,6 @@ if dep_webrtc_audio_processing.found() and get_option('plugin-rtp-webrtc-audio-p elif get_option('plugin-rtp-webrtc-audio-processing').enabled() error('No compatible webrtc-audio-processing found, but plugin-rtp-webrtc-audio-processing option set.') 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 meson.get_compiler('vala').version().version_compare('<0.56.1') vala_args += ['--vapidir', meson.current_source_dir() / 'vapi'] endif diff --git a/plugins/rtp/src/codec_util.vala b/plugins/rtp/src/codec_util.vala index 12fcfed7..9f21f5a6 100644 --- a/plugins/rtp/src/codec_util.vala +++ b/plugins/rtp/src/codec_util.vala @@ -280,9 +280,7 @@ public class Dino.Plugins.Rtp.CodecUtil { } public static string? get_depay_args(string media, string codec, string encode, JingleRtp.PayloadType? payload_type) { -#if GST_1_18 if (codec == "vp8") return " wait-for-keyframe=true"; -#endif return null; } diff --git a/plugins/rtp/src/plugin.vala b/plugins/rtp/src/plugin.vala index a94c0ba0..d09b7edf 100644 --- a/plugins/rtp/src/plugin.vala +++ b/plugins/rtp/src/plugin.vala @@ -243,13 +243,11 @@ public class Dino.Plugins.Rtp.Plugin : RootInterface, VideoCallPlugin, Object { device = new Device(this, gst_device); devices.add(device); break; -#if GST_1_16 case Gst.MessageType.DEVICE_CHANGED: message.parse_device_changed(out gst_device, out old_gst_device); device = devices.first_match((it) => it.matches(old_gst_device)); if (device != null) device.update(gst_device); break; -#endif case Gst.MessageType.DEVICE_REMOVED: message.parse_device_removed(out gst_device); device = devices.first_match((it) => it.matches(gst_device)); diff --git a/plugins/rtp/src/stream.vala b/plugins/rtp/src/stream.vala index aa2b080d..9a46d9ff 100644 --- a/plugins/rtp/src/stream.vala +++ b/plugins/rtp/src/stream.vala @@ -102,9 +102,7 @@ public class Dino.Plugins.Rtp.Stream : Xmpp.Xep.JingleRtp.Stream { send_rtp.drop = true; send_rtp.wait_on_eos = false; send_rtp.new_sample.connect(on_new_sample); -#if GST_1_20 send_rtp.new_serialized_event.connect(on_new_event); -#endif send_rtp.connect("signal::eos", on_eos_static, this); pipe.add(send_rtp); @@ -299,7 +297,7 @@ public class Dino.Plugins.Rtp.Stream : Xmpp.Xep.JingleRtp.Stream { bool flip = false; uint8 rotation = 0; -#if GST_1_20 + private bool on_new_event(Gst.App.Sink sink) { if (sink == null || sink != send_rtp) { return false; @@ -349,7 +347,7 @@ public class Dino.Plugins.Rtp.Stream : Xmpp.Xep.JingleRtp.Stream { } return false; } -#endif + private Gst.FlowReturn on_new_sample(Gst.App.Sink sink) { if (sink == null) { @@ -373,14 +371,11 @@ public class Dino.Plugins.Rtp.Stream : Xmpp.Xep.JingleRtp.Stream { next_timestamp_offset_stamp = get_monotonic_time(); rtp_buffer.unmap(); } -#if GLIB_2_64 if (our_ssrc != buffer_ssrc) { warning_once("Sending RTP %s buffer seq %u with SSRC %u when our ssrc is %u", media, buffer_seq, buffer_ssrc, our_ssrc); } -#endif } -#if GST_1_20 if (sink == send_rtp) { Xmpp.Xep.JingleRtp.HeaderExtension? ext = header_extensions.first_match((it) => it.uri == "urn:3gpp:video-orientation"); if (ext != null) { @@ -396,7 +391,6 @@ public class Dino.Plugins.Rtp.Stream : Xmpp.Xep.JingleRtp.Stream { } } } -#endif prepare_local_crypto(); @@ -574,18 +568,7 @@ public class Dino.Plugins.Rtp.Stream : Xmpp.Xep.JingleRtp.Stream { on_recv_rtcp_data(bytes); return; } -#if GST_1_16 - { - Gst.Buffer buffer = new Gst.Buffer.wrapped_bytes(bytes); - Gst.RTP.Buffer rtp_buffer; - uint buffer_ssrc = 0, buffer_seq = 0; - if (Gst.RTP.Buffer.map(buffer, Gst.MapFlags.READ, out rtp_buffer)) { - buffer_ssrc = rtp_buffer.get_ssrc(); - buffer_seq = rtp_buffer.get_seq(); - rtp_buffer.unmap(); - } - } -#endif + if (push_recv_data) { prepare_remote_crypto(); @@ -598,15 +581,13 @@ public class Dino.Plugins.Rtp.Stream : Xmpp.Xep.JingleRtp.Stream { return; } } else { -#if GST_1_16 buffer = new Gst.Buffer.wrapped_bytes(bytes); -#else - buffer = new Gst.Buffer.wrapped(bytes.get_data()); -#endif } Gst.RTP.Buffer rtp_buffer; if (Gst.RTP.Buffer.map(buffer, Gst.MapFlags.READ, out rtp_buffer)) { + uint buffer_ssrc = rtp_buffer.get_ssrc(); + uint buffer_seq = rtp_buffer.get_seq(); if (rtp_buffer.get_extension()) { Xmpp.Xep.JingleRtp.HeaderExtension? ext = header_extensions.first_match((it) => it.uri == "urn:3gpp:video-orientation"); if (ext != null) { @@ -632,12 +613,7 @@ public class Dino.Plugins.Rtp.Stream : Xmpp.Xep.JingleRtp.Stream { rtp_buffer.unmap(); } -#if VALA_0_50 recv_rtp.push_buffer((owned) buffer); -#else - Gst.FlowReturn ret; - GLib.Signal.emit_by_name(recv_rtp, "push-buffer", buffer, out ret); -#endif } } @@ -654,19 +630,10 @@ public class Dino.Plugins.Rtp.Stream : Xmpp.Xep.JingleRtp.Stream { return; } } else { -#if GST_1_16 buffer = new Gst.Buffer.wrapped_bytes(bytes); -#else - buffer = new Gst.Buffer.wrapped(bytes.get_data()); -#endif } -#if VALA_0_50 recv_rtcp.push_buffer((owned) buffer); -#else - Gst.FlowReturn ret; - GLib.Signal.emit_by_name(recv_rtcp, "push-buffer", buffer, out ret); -#endif } } diff --git a/plugins/rtp/src/video_widget.vala b/plugins/rtp/src/video_widget.vala index 5557ec76..af6eb14e 100644 --- a/plugins/rtp/src/video_widget.vala +++ b/plugins/rtp/src/video_widget.vala @@ -75,17 +75,10 @@ public class Dino.Plugins.Rtp.Sink : Gst.Video.Sink { set_drop_out_of_segment(false); } -#if GST_1_20 public override bool set_info(Gst.Caps caps, Gst.Video.Info info) { this.info = info; return true; } -#else - public override bool set_caps(Gst.Caps caps) { - base.set_caps(caps); - return info.from_caps(caps); - } -#endif public override void get_times(Gst.Buffer buffer, out Gst.ClockTime start, out Gst.ClockTime end) { if (buffer.pts != -1) { @@ -250,21 +243,13 @@ public class Dino.Plugins.Rtp.VideoWidget : Gtk.Widget, Dino.Plugins.VideoCallWi if (connected_device == null) return; plugin.pause(); pipe.add(sink); -#if GST_1_20 prepare = Gst.parse_bin_from_description(@"videoflip video-direction=auto name=video_widget_$(id)_orientation ! videoflip method=horizontal-flip name=video_widget_$(id)_flip ! videoconvert name=video_widget_$(id)_convert", true); -#else - prepare = Gst.parse_bin_from_description(@"videoflip method=horizontal-flip name=video_widget_$(id)_flip ! videoconvert name=video_widget_$(id)_convert", true); -#endif prepare.name = @"video_widget_$(id)_prepare"; -#if GST_1_20 if (prepare is Gst.Bin) { ((Gst.Bin) prepare).get_by_name(@"video_widget_$(id)_flip").get_static_pad("sink").notify["caps"].connect(input_caps_changed); } else { -#endif prepare.get_static_pad("sink").notify["caps"].connect(input_caps_changed); -#if GST_1_20 } -#endif pipe.add(prepare); connected_device_element = connected_device.link_source(); connected_device_element.link(prepare); diff --git a/xmpp-vala/meson.build b/xmpp-vala/meson.build index 971f77fa..210526a5 100644 --- a/xmpp-vala/meson.build +++ b/xmpp-vala/meson.build @@ -147,9 +147,6 @@ c_args = vala_c_args + [ vala_args = [ '--vapidir', meson.current_source_dir() / 'vapi', ] -if meson.get_compiler('vala').version().version_compare('=0.56.11') - vala_args += ['-D', 'VALA_0_56_11'] -endif 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], install_rpath: default_install_rpath) dep_xmpp_vala = declare_dependency(link_with: lib_xmpp_vala, include_directories: include_directories('.')) diff --git a/xmpp-vala/src/core/direct_tls_xmpp_stream.vala b/xmpp-vala/src/core/direct_tls_xmpp_stream.vala index 2f159ab6..55488eb5 100644 --- a/xmpp-vala/src/core/direct_tls_xmpp_stream.vala +++ b/xmpp-vala/src/core/direct_tls_xmpp_stream.vala @@ -19,9 +19,7 @@ public class Xmpp.DirectTlsXmppStream : TlsXmppStream { debug("Connecting to %s:%i (tls)", host, port); IOStream? io_stream = yield client.connect_to_host_async(host, port, cancellable); TlsConnection tls_connection = TlsClientConnection.new(io_stream, new NetworkAddress(remote_name.to_string(), port)); -#if GLIB_2_60 tls_connection.set_advertised_protocols(ADVERTISED_PROTOCOLS); -#endif tls_connection.accept_certificate.connect(on_invalid_certificate); tls_connection.accept_certificate.connect((cert, flags) => on_invalid_cert.func(cert, flags)); reset_stream(tls_connection); diff --git a/xmpp-vala/src/core/module_flag.vala b/xmpp-vala/src/core/module_flag.vala index 76ae4dc1..95547852 100644 --- a/xmpp-vala/src/core/module_flag.vala +++ b/xmpp-vala/src/core/module_flag.vala @@ -10,12 +10,7 @@ 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) { @@ -39,12 +34,7 @@ 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) { -- cgit v1.3 From 630b1368331ef0034b343bd2f00b116c8cd90391 Mon Sep 17 00:00:00 2001 From: Marvin W Date: Sun, 4 Oct 2026 17:49:16 +0200 Subject: Fix some warnings --- .../omemo/ui/encryption_preferences_entry.vala | 4 + main/src/plugins/omemo/ui/manage_key_dialog.vala | 3 + main/src/ui/main_window.vala | 24 +++--- main/src/ui/util/data_forms.vala | 2 +- xmpp-vala/src/core/stanza_attribute.vala | 16 +++- xmpp-vala/src/core/stanza_node.vala | 92 ++++++++++++---------- xmpp-vala/src/core/stream_connect.vala | 10 ++- xmpp-vala/src/module/bookmarks_provider.vala | 8 +- xmpp-vala/src/module/iq/module.vala | 2 +- xmpp-vala/src/module/sasl.vala | 14 ++-- xmpp-vala/src/module/session.vala | 10 ++- xmpp-vala/src/module/xep/0004_data_forms.vala | 3 +- .../module/xep/0030_service_discovery/module.vala | 4 +- xmpp-vala/src/module/xep/0045_muc/module.vala | 24 +++--- xmpp-vala/src/module/xep/0048_bookmarks.vala | 10 +-- .../src/module/xep/0049_private_xml_storage.vala | 4 +- xmpp-vala/src/module/xep/0054_vcard/module.vala | 2 +- xmpp-vala/src/module/xep/0060_pubsub.vala | 10 +-- .../src/module/xep/0065_socks5_bytestreams.vala | 2 +- .../src/module/xep/0077_in_band_registration.vala | 6 +- .../src/module/xep/0082_date_time_profiles.vala | 4 +- xmpp-vala/src/module/xep/0084_user_avatars.vala | 2 +- .../src/module/xep/0166_jingle/component.vala | 12 ++- xmpp-vala/src/module/xep/0166_jingle/content.vala | 12 ++- .../src/module/xep/0166_jingle/jingle_flag.vala | 7 +- xmpp-vala/src/module/xep/0166_jingle/session.vala | 7 +- .../0176_jingle_ice_udp/transport_parameters.vala | 6 +- .../src/module/xep/0191_blocking_command.vala | 8 +- xmpp-vala/src/module/xep/0199_ping.vala | 2 +- .../xep/0215_external_service_discovery.vala | 2 +- .../src/module/xep/0234_jingle_file_transfer.vala | 2 +- .../module/xep/0260_jingle_socks5_bytestreams.vala | 10 ++- xmpp-vala/src/module/xep/0272_muji.vala | 7 +- xmpp-vala/src/module/xep/0280_message_carbons.vala | 12 ++- .../xep/0313_2_message_archive_management.vala | 4 +- .../xep/0313_message_archive_management.vala | 2 +- .../module/xep/0392_consistent_color/hsluv.vala | 6 +- xmpp-vala/src/module/xep/0402_bookmarks2.vala | 6 +- xmpp-vala/src/module/xep/0461_replies.vala | 1 - xmpp-vala/tests/color.vala | 6 +- xmpp-vala/tests/stanza.vala | 48 ++++++----- xmpp-vala/tests/testcase.vala | 13 ++- 42 files changed, 271 insertions(+), 158 deletions(-) (limited to 'main/src/ui') diff --git a/main/src/plugins/omemo/ui/encryption_preferences_entry.vala b/main/src/plugins/omemo/ui/encryption_preferences_entry.vala index 55faecac..7f53a1b2 100644 --- a/main/src/plugins/omemo/ui/encryption_preferences_entry.vala +++ b/main/src/plugins/omemo/ui/encryption_preferences_entry.vala @@ -224,6 +224,10 @@ public class OmemoPreferencesWidget : Adw.PreferencesGroup { case TrustLevel.VERIFIED: trust_str = _("Verified"); break; + case TrustLevel.TRUSTED: + case TrustLevel.UNKNOWN: + default: + break; } action_row.add_suffix(new Label(trust_str)); diff --git a/main/src/plugins/omemo/ui/manage_key_dialog.vala b/main/src/plugins/omemo/ui/manage_key_dialog.vala index 65033999..59e6db24 100644 --- a/main/src/plugins/omemo/ui/manage_key_dialog.vala +++ b/main/src/plugins/omemo/ui/manage_key_dialog.vala @@ -141,6 +141,9 @@ public class ManageKeyDialog : Gtk.Dialog { main_desc_label.set_markup(_("This key is currently %s.").printf(""+_("rejected")+"")+" "+_("This means it cannot be used by %s to decipher your messages, and you won't see messages encrypted with it.").printf(@"$(device[db.identity_meta.address_name])")); main_action_list.append(accept_row); break; + case TrustLevel.UNKNOWN: + default: + break; } //Row clicked - go to appropriate screen diff --git a/main/src/ui/main_window.vala b/main/src/ui/main_window.vala index d2a23954..da6960ea 100644 --- a/main/src/ui/main_window.vala +++ b/main/src/ui/main_window.vala @@ -11,21 +11,21 @@ public class MainWindow : Adw.ApplicationWindow { public signal void conversation_selected(Conversation conversation); - [GtkChild] public Stack stack; - [GtkChild] public Adw.NavigationSplitView navigation_split_view; + [GtkChild] public unowned Stack stack; + [GtkChild] public unowned Adw.NavigationSplitView navigation_split_view; - [GtkChild] public MenuButton add_button; - [GtkChild] public MenuButton menu_button; + [GtkChild] public unowned MenuButton add_button; + [GtkChild] public unowned MenuButton menu_button; - [GtkChild] public Adw.HeaderBar conversation_headerbar; - [GtkChild] public Adw.WindowTitle conversation_window_title; + [GtkChild] public unowned Adw.HeaderBar conversation_headerbar; + [GtkChild] public unowned Adw.WindowTitle conversation_window_title; - [GtkChild] public ConversationView conversation_view; - [GtkChild] public ConversationSelector conversation_selector; - [GtkChild] public Adw.Flap search_flap; - [GtkChild] private Stack left_stack; - [GtkChild] private Stack right_stack; - [GtkChild] public Adw.Bin search_frame; + [GtkChild] public unowned ConversationView conversation_view; + [GtkChild] public unowned ConversationSelector conversation_selector; + [GtkChild] public unowned Adw.Flap search_flap; + [GtkChild] private unowned Stack left_stack; + [GtkChild] private unowned Stack right_stack; + [GtkChild] public unowned Adw.Bin search_frame; public GlobalSearch global_search; diff --git a/main/src/ui/util/data_forms.vala b/main/src/ui/util/data_forms.vala index 7fb9b87d..0b91da15 100644 --- a/main/src/ui/util/data_forms.vala +++ b/main/src/ui/util/data_forms.vala @@ -18,7 +18,7 @@ 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; + if (field.type_ == DataForms.DataForm.Type.UNKNOWN) return null; ViewModel.PreferencesRow.Any? view_model = null; diff --git a/xmpp-vala/src/core/stanza_attribute.vala b/xmpp-vala/src/core/stanza_attribute.vala index 6c6ac579..ba1616a5 100644 --- a/xmpp-vala/src/core/stanza_attribute.vala +++ b/xmpp-vala/src/core/stanza_attribute.vala @@ -56,7 +56,13 @@ public class StanzaAttribute : StanzaEntry { if (ns_uri == state.current_ns_uri || (ns_uri == XMLNS_URI && name == "xmlns")) { return printf(ATTRIBUTE_XML_NO_NS_FORMAT, true); } else { - return printf(ATTRIBUTE_XML_FORMAT, false, state.find_name((!)ns_uri)); + string ns_name = "_"; + try { + ns_name = state.find_name((!)ns_uri); + } catch (IOError e) { + warning("Error finding name: %s", e.message); + } + return printf(ATTRIBUTE_XML_FORMAT, false, ns_name); } } @@ -65,7 +71,13 @@ public class StanzaAttribute : StanzaEntry { if (ns_uri == state.current_ns_uri || (ns_uri == XMLNS_URI && name == "xmlns")) { return printf(ATTRIBUTE_XML_ANSI_NO_NS_FORMAT, true); } else { - return printf(ATTRIBUTE_XML_ANSI_FORMAT, false, state.find_name((!)ns_uri)); + string ns_name = "_"; + try { + ns_name = state.find_name((!)ns_uri); + } catch (IOError e) { + warning("Error finding name: %s", e.message); + } + return printf(ATTRIBUTE_XML_ANSI_FORMAT, false, ns_name); } } } diff --git a/xmpp-vala/src/core/stanza_node.vala b/xmpp-vala/src/core/stanza_node.vala index f44657f1..7a68da62 100644 --- a/xmpp-vala/src/core/stanza_node.vala +++ b/xmpp-vala/src/core/stanza_node.vala @@ -18,54 +18,62 @@ public abstract class StanzaEntry { public string? encoded_val { owned get { if (val == null) return null; - return ENCODABLE_VALUE_CHARS.replace_eval(val, -1, 0, 0, (match_info, result_builder) => { - int start; - match_info.fetch_pos(0, out start, null); - unowned string src = match_info.get_string(); - switch (src[start]) { - case '&': result_builder.append("&"); break; - case '"': result_builder.append("""); break; - case '\'': result_builder.append("'"); break; - case '<': result_builder.append("<"); break; - case '>': result_builder.append(">"); break; - default: error("Unreachable"); break; - } - return false; - }); + try { + return ENCODABLE_VALUE_CHARS.replace_eval(val, -1, 0, 0, (match_info, result_builder) => { + int start; + match_info.fetch_pos(0, out start, null); + unowned string src = match_info.get_string(); + switch (src[start]) { + case '&': result_builder.append("&"); break; + case '"': result_builder.append("""); break; + case '\'': result_builder.append("'"); break; + case '<': result_builder.append("<"); break; + case '>': result_builder.append(">"); break; + default: error("Unreachable"); break; + } + return false; + }); + } catch (RegexError e) { + error("Error encoding entities in string \"%s\": %s", val, e.message); + } } set { if (value == null) { val = null; return; } - val = DECODABLE_VALUE_SEQUENCES.replace_eval(value, -1, 0, 0, (match_info, result_builder) => { - int start, end; - match_info.fetch_pos(0, out start, out end); - unowned string src = match_info.get_string(); - switch (src[start+1]) { - case 'g': result_builder.append_c('>'); break; - case 'l': result_builder.append_c('<'); break; - case 'q': result_builder.append_c('"'); break; - case 'a': - switch (src[start+2]) { - case 'p': result_builder.append_c('\''); break; - case 'm': result_builder.append_c('&'); break; - default: critical("Invalid encoding: %s in %s", match_info.fetch(0), src); break; - } - break; - case '#': - unichar num = -1; - if (src[start+2] == 'x') { - src.substring(start+3, start-end-3).scanf("%x", &num); - } else { - num = int.parse(src.substring(start+2, start-end-2)); - } - result_builder.append_unichar(num); - break; - default: critical("Invalid encoding: %s in %s", match_info.fetch(0), src); break; - } - return false; - }); + try { + val = DECODABLE_VALUE_SEQUENCES.replace_eval(value, -1, 0, 0, (match_info, result_builder) => { + int start, end; + match_info.fetch_pos(0, out start, out end); + unowned string src = match_info.get_string(); + switch (src[start + 1]) { + case 'g': result_builder.append_c('>'); break; + case 'l': result_builder.append_c('<'); break; + case 'q': result_builder.append_c('"'); break; + case 'a': + switch (src[start + 2]) { + case 'p': result_builder.append_c('\''); break; + case 'm': result_builder.append_c('&'); break; + default: critical("Invalid encoding: %s in %s", match_info.fetch(0), src); break; + } + break; + case '#': + unichar num = -1; + if (src[start + 2] == 'x') { + src.substring(start + 3, start - end - 3).scanf("%x", &num); + } else { + num = int.parse(src.substring(start + 2, start - end - 2)); + } + result_builder.append_unichar(num); + break; + default: critical("Invalid encoding: %s in %s", match_info.fetch(0), src); break; + } + return false; + }); + } catch (RegexError e) { + error("Error decoding entities in string \"%s\": %s", value, e.message); + } } } diff --git a/xmpp-vala/src/core/stream_connect.vala b/xmpp-vala/src/core/stream_connect.vala index 7e3843fa..a496fc3c 100644 --- a/xmpp-vala/src/core/stream_connect.vala +++ b/xmpp-vala/src/core/stream_connect.vala @@ -15,7 +15,7 @@ namespace Xmpp { public async XmppStreamResult establish_stream(Jid bare_jid, Gee.List modules, string? log_options, owned TlsXmppStream.OnInvalidCert on_invalid_cert) { Jid remote = bare_jid.domain_jid; - TlsXmppStream.OnInvalidCertWrapper on_invalid_cert_wrapper = new TlsXmppStream.OnInvalidCertWrapper(on_invalid_cert); + TlsXmppStream.OnInvalidCertWrapper on_invalid_cert_wrapper = new TlsXmppStream.OnInvalidCertWrapper((owned) on_invalid_cert); //Lookup xmpp-client and xmpps-client SRV records GLib.List? targets = new GLib.List(); @@ -72,7 +72,13 @@ namespace Xmpp { connection_timeout_id = Timeout.add_seconds(30, () => { warning("Connection attempt timed out"); - stream.disconnect(); + stream.disconnect.begin((_, res) => { + try { + stream.disconnect.end(res); + } catch (IOError e) { + debug("Error while disconnecting: %s", e.message); + } + }); connection_timeout_id = 0; return Source.REMOVE; }); diff --git a/xmpp-vala/src/module/bookmarks_provider.vala b/xmpp-vala/src/module/bookmarks_provider.vala index 6ddcc5af..458ff183 100644 --- a/xmpp-vala/src/module/bookmarks_provider.vala +++ b/xmpp-vala/src/module/bookmarks_provider.vala @@ -8,10 +8,10 @@ public interface BookmarksProvider : Object { public signal void conference_changed(XmppStream stream, Conference conferences); public signal void received_conferences(XmppStream stream, Set conferences); - public async abstract async Set? get_conferences(XmppStream stream); - public async abstract void add_conference(XmppStream stream, Conference conference); - public async abstract void remove_conference(XmppStream stream, Conference conference); - public async abstract void replace_conference(XmppStream stream, Jid muc_jid, Conference modified_conference); + public async abstract async Set? get_conferences(XmppStream stream) throws IOError; + public async abstract void add_conference(XmppStream stream, Conference conference) throws IOError; + public async abstract void remove_conference(XmppStream stream, Conference conference) throws IOError; + public async abstract void replace_conference(XmppStream stream, Jid muc_jid, Conference modified_conference) throws IOError; } } diff --git a/xmpp-vala/src/module/iq/module.vala b/xmpp-vala/src/module/iq/module.vala index bb9260c9..917f6660 100644 --- a/xmpp-vala/src/module/iq/module.vala +++ b/xmpp-vala/src/module/iq/module.vala @@ -23,7 +23,7 @@ namespace Xmpp.Iq { Idle.add(send_iq_async.callback); } }); - stream.write_async(iq.stanza, io_priority, cancellable); + yield stream.write_async(iq.stanza, io_priority, cancellable); ulong cancelled_handler_id = 0; if (cancellable != null) { cancelled_handler_id = cancellable.cancelled.connect(() => Idle.add(send_iq_async.callback)); diff --git a/xmpp-vala/src/module/sasl.vala b/xmpp-vala/src/module/sasl.vala index 035cc723..f0c2d6ed 100644 --- a/xmpp-vala/src/module/sasl.vala +++ b/xmpp-vala/src/module/sasl.vala @@ -73,10 +73,14 @@ namespace Xmpp.Sasl { } else if (node.name == "challenge" && stream.has_flag(Flag.IDENTITY)) { Flag flag = stream.get_flag(Flag.IDENTITY); if (flag.mechanism == Mechanism.SCRAM_SHA_1) { - var challenge_response = compute_challenge_response(node.get_string_content(), flag.password, flag.client_nonce, flag.name); - flag.server_signature = challenge_response.expected_server_signature; - stream.write(new StanzaNode.build("response", NS_URI).add_self_xmlns() - .put_node(new StanzaNode.text(Base64.encode((uchar[]) (challenge_response.response).to_utf8())))); + try { + var challenge_response = compute_challenge_response(node.get_string_content(), flag.password, flag.client_nonce, flag.name); + flag.server_signature = challenge_response.expected_server_signature; + stream.write(new StanzaNode.build("response", NS_URI).add_self_xmlns() + .put_node(new StanzaNode.text(Base64.encode((uchar[]) (challenge_response.response).to_utf8())))); + } catch (Error e) { + warning("Failed processing challenge: %s", e.message); + } } } } @@ -165,7 +169,7 @@ namespace Xmpp.Sasl { public string response { get; set; } } - public static ChallengeResponse compute_challenge_response(string challenge, string password, string client_nonce, string name) { + public static ChallengeResponse compute_challenge_response(string challenge, string password, string client_nonce, string name) throws Error { string challenge_base64 = (string) Base64.decode(challenge); string? server_nonce = null; uint8[] salt = null; diff --git a/xmpp-vala/src/module/session.vala b/xmpp-vala/src/module/session.vala index b1436d1e..fa947624 100644 --- a/xmpp-vala/src/module/session.vala +++ b/xmpp-vala/src/module/session.vala @@ -28,9 +28,13 @@ public class Module : XmppStreamNegotiationModule { stream.add_flag(new Flag()); Iq.Stanza iq = new Iq.Stanza.set(new StanzaNode.build("session", NS_URI).add_self_xmlns()) { to=stream.remote_name }; - Iq.Stanza result_iq = yield stream.get_module(Iq.Module.IDENTITY).send_iq_async(stream, iq); - if (!result_iq.is_error()) { - stream.get_flag(Flag.IDENTITY).finished = true; + try { + Iq.Stanza result_iq = yield stream.get_module(Iq.Module.IDENTITY).send_iq_async(stream, iq); + if (!result_iq.is_error()) { + stream.get_flag(Flag.IDENTITY).finished = true; + } + } catch (IOError e) { + warning("Error establishing legacy session: %s", e.message); } } } diff --git a/xmpp-vala/src/module/xep/0004_data_forms.vala b/xmpp-vala/src/module/xep/0004_data_forms.vala index 6b5624da..f481e174 100644 --- a/xmpp-vala/src/module/xep/0004_data_forms.vala +++ b/xmpp-vala/src/module/xep/0004_data_forms.vala @@ -18,6 +18,7 @@ public class DataForm { } public enum Type { + UNKNOWN, BOOLEAN, FIXED, HIDDEN, @@ -44,7 +45,7 @@ public class DataForm { get { return node.get_attribute("label", NS_URI); } set { node.set_attribute("label", value); } } - public virtual Type? type_ { get; internal set; default=null; } + public virtual Type type_ { get; internal set; default=Type.UNKNOWN; } public string? var { get { return node.get_attribute("var", NS_URI); } set { node.set_attribute("var", value); } diff --git a/xmpp-vala/src/module/xep/0030_service_discovery/module.vala b/xmpp-vala/src/module/xep/0030_service_discovery/module.vala index 537e460b..1ba8ac3a 100644 --- a/xmpp-vala/src/module/xep/0030_service_discovery/module.vala +++ b/xmpp-vala/src/module/xep/0030_service_discovery/module.vala @@ -45,7 +45,7 @@ public class Module : XmppStreamModule, Iq.Handler { return yield this.cache.get_entity_identities(jid); } - public async InfoResult? request_info(XmppStream stream, Jid jid) { + public async InfoResult? request_info(XmppStream stream, Jid jid) throws IOError { var future = active_info_requests[jid]; if (future == null) { var promise = new Promise(); @@ -69,7 +69,7 @@ public class Module : XmppStreamModule, Iq.Handler { } } - public async ItemsResult? request_items(XmppStream stream, Jid jid) { + public async ItemsResult? request_items(XmppStream stream, Jid jid) throws IOError { StanzaNode query_node = new StanzaNode.build("query", NS_URI_ITEMS).add_self_xmlns(); Iq.Stanza iq = new Iq.Stanza.get(query_node) { to=jid }; diff --git a/xmpp-vala/src/module/xep/0045_muc/module.vala b/xmpp-vala/src/module/xep/0045_muc/module.vala index 76045172..e4c066f0 100644 --- a/xmpp-vala/src/module/xep/0045_muc/module.vala +++ b/xmpp-vala/src/module/xep/0045_muc/module.vala @@ -215,7 +215,7 @@ public class Module : XmppStreamModule { stream.get_module(Iq.Module.IDENTITY).send_iq(stream, iq); } - public async void change_affiliation(XmppStream stream, Jid muc_jid, Jid? user_jid, string? nick, string new_affiliation) { + public async void change_affiliation(XmppStream stream, Jid muc_jid, Jid? user_jid, string? nick, string new_affiliation) throws IOError { StanzaNode item_node = new StanzaNode.build("item", NS_URI_ADMIN) .put_attribute("affiliation", new_affiliation, NS_URI_ADMIN); if (user_jid != null) { @@ -229,7 +229,7 @@ public class Module : XmppStreamModule { yield stream.get_module(Iq.Module.IDENTITY).send_iq_async(stream, iq); } - public async DataForms.DataForm? get_config_form(XmppStream stream, Jid jid) { + public async DataForms.DataForm? get_config_form(XmppStream stream, Jid jid) throws IOError { Iq.Stanza get_iq = new Iq.Stanza.get(new StanzaNode.build("query", NS_URI_OWNER).add_self_xmlns()) { to=jid }; Iq.Stanza result_iq = yield stream.get_module(Iq.Module.IDENTITY).send_iq_async(stream, get_iq); @@ -241,7 +241,7 @@ public class Module : XmppStreamModule { return null; } - public async void set_config_form(XmppStream stream, Jid jid, DataForms.DataForm data_form) { + public async void set_config_form(XmppStream stream, Jid jid, DataForms.DataForm data_form) throws IOError { StanzaNode stanza_node = new StanzaNode.build("query", NS_URI_OWNER); stanza_node.add_self_xmlns().put_node(data_form.get_submit_node()); Iq.Stanza set_iq = new Iq.Stanza.set(stanza_node) { to=jid }; @@ -420,7 +420,7 @@ public class Module : XmppStreamModule { yield query_affiliation(stream, muc_jid, "owner"); } - private async Gee.List? query_affiliation(XmppStream stream, Jid jid, string affiliation) { + private async Gee.List? query_affiliation(XmppStream stream, Jid jid, string affiliation) throws IOError { Iq.Stanza iq = new Iq.Stanza.get( new StanzaNode.build("query", NS_URI_ADMIN) .add_self_xmlns() @@ -538,14 +538,18 @@ public class ReceivedPipelineListener : StanzaListener { if (item_nodes.size > 0) { var affiliations = new HashMap(); foreach (StanzaNode item_node in item_nodes) { - string jid_str = item_node.get_attribute("jid", Muc.NS_URI_USER); - Jid real_jid = Jid.from_string(jid_str); - string affiliation_str = item_node.get_attribute("affiliation", Muc.NS_URI_USER); - Muc.Affiliation affiliation = Muc.Module.parse_affiliation(affiliation_str); + try { + string jid_str = item_node.get_attribute("jid", Muc.NS_URI_USER); + Jid real_jid = Jid.from_string(jid_str); + string affiliation_str = item_node.get_attribute("affiliation", Muc.NS_URI_USER); + Muc.Affiliation affiliation = Muc.Module.parse_affiliation(affiliation_str); - affiliations[real_jid] = affiliation; + affiliations[real_jid] = affiliation; - stream.get_flag(Flag.IDENTITY).set_offline_member(message.from, real_jid, affiliation); + stream.get_flag(Flag.IDENTITY).set_offline_member(message.from, real_jid, affiliation); + } catch (InvalidJidError e) { + warning("Received invalid affiliation update: %s", e.message); + } } string? mav_since = MucAffiliationsVersioning.get_mav_since(x_node); diff --git a/xmpp-vala/src/module/xep/0048_bookmarks.vala b/xmpp-vala/src/module/xep/0048_bookmarks.vala index 46814e4a..dc009a1e 100644 --- a/xmpp-vala/src/module/xep/0048_bookmarks.vala +++ b/xmpp-vala/src/module/xep/0048_bookmarks.vala @@ -6,7 +6,7 @@ private const string NS_URI = "storage:bookmarks"; public class Module : BookmarksProvider, XmppStreamModule { public static ModuleIdentity IDENTITY = new ModuleIdentity(NS_URI, "0048_bookmarks_module"); - public async Set? get_conferences(XmppStream stream) { + public async Set? get_conferences(XmppStream stream) throws IOError { StanzaNode get_node = new StanzaNode.build("storage", NS_URI).add_self_xmlns(); StanzaNode? result_node = yield stream.get_module(PrivateXmlStorage.Module.IDENTITY).retrieve(stream, get_node); if (result_node == null) return null; @@ -20,7 +20,7 @@ public class Module : BookmarksProvider, XmppStreamModule { return ret; } - private async void set_conferences(XmppStream stream, Set conferences) { + private async void set_conferences(XmppStream stream, Set conferences) throws IOError { StanzaNode storage_node = (new StanzaNode.build("storage", NS_URI)).add_self_xmlns(); foreach (Conference conference in conferences) { Bookmarks1Conference? bookmarks1conference = conference as Bookmarks1Conference; @@ -45,13 +45,13 @@ public class Module : BookmarksProvider, XmppStreamModule { stream.get_module(Module.IDENTITY).received_conferences(stream, conferences); } - public async void add_conference(XmppStream stream, Conference conference) { + public async void add_conference(XmppStream stream, Conference conference) throws IOError { Set? conferences = yield get_conferences(stream); conferences.add(conference); yield set_conferences(stream, conferences); } - public async void replace_conference(XmppStream stream, Jid muc_jid, Conference modified_conference) { + public async void replace_conference(XmppStream stream, Jid muc_jid, Conference modified_conference) throws IOError { Set? conferences = yield get_conferences(stream); foreach (Conference conference in conferences) { if (conference.jid.equals(muc_jid)) { @@ -64,7 +64,7 @@ public class Module : BookmarksProvider, XmppStreamModule { yield set_conferences(stream, conferences); } - public async void remove_conference(XmppStream stream, Conference conference_remove) { + public async void remove_conference(XmppStream stream, Conference conference_remove) throws IOError { Set? conferences = yield get_conferences(stream); conferences.remove(conference_remove); yield set_conferences(stream, conferences); diff --git a/xmpp-vala/src/module/xep/0049_private_xml_storage.vala b/xmpp-vala/src/module/xep/0049_private_xml_storage.vala index 81ccde9b..a6501505 100644 --- a/xmpp-vala/src/module/xep/0049_private_xml_storage.vala +++ b/xmpp-vala/src/module/xep/0049_private_xml_storage.vala @@ -6,13 +6,13 @@ namespace Xmpp.Xep.PrivateXmlStorage { public class Module : XmppStreamModule { public static ModuleIdentity IDENTITY = new ModuleIdentity(NS_URI, "0049_private_xml_storage"); - public async void store(XmppStream stream, StanzaNode node) { + public async void store(XmppStream stream, StanzaNode node) throws IOError { StanzaNode queryNode = new StanzaNode.build("query", NS_URI).add_self_xmlns().put_node(node); Iq.Stanza iq = new Iq.Stanza.set(queryNode); yield stream.get_module(Iq.Module.IDENTITY).send_iq_async(stream, iq); } - public async StanzaNode? retrieve(XmppStream stream, StanzaNode node) { + public async StanzaNode? retrieve(XmppStream stream, StanzaNode node) throws IOError { StanzaNode queryNode = new StanzaNode.build("query", NS_URI).add_self_xmlns().put_node(node); Iq.Stanza iq = new Iq.Stanza.get(queryNode); Iq.Stanza iq_result = yield stream.get_module(Iq.Module.IDENTITY).send_iq_async(stream, iq); diff --git a/xmpp-vala/src/module/xep/0054_vcard/module.vala b/xmpp-vala/src/module/xep/0054_vcard/module.vala index 4df1d9a5..4c02a2d1 100644 --- a/xmpp-vala/src/module/xep/0054_vcard/module.vala +++ b/xmpp-vala/src/module/xep/0054_vcard/module.vala @@ -2,7 +2,7 @@ namespace Xmpp.Xep.VCard { private const string NS_URI = "vcard-temp"; private const string NS_URI_UPDATE = NS_URI + ":x:update"; -public async Bytes? fetch_image(XmppStream stream, Jid jid, string hash) { +public async Bytes? fetch_image(XmppStream stream, Jid jid, string hash) throws IOError { Iq.Stanza iq = new Iq.Stanza.get(new StanzaNode.build("vCard", NS_URI).add_self_xmlns()) { to=jid }; Iq.Stanza iq_res = yield stream.get_module(Iq.Module.IDENTITY).send_iq_async(stream, iq); diff --git a/xmpp-vala/src/module/xep/0060_pubsub.vala b/xmpp-vala/src/module/xep/0060_pubsub.vala index c9af4263..88b9cd31 100644 --- a/xmpp-vala/src/module/xep/0060_pubsub.vala +++ b/xmpp-vala/src/module/xep/0060_pubsub.vala @@ -41,7 +41,7 @@ namespace Xmpp.Xep.Pubsub { retract_listeners.unset(node); } - public async Gee.List? request_all(XmppStream stream, Jid? jid, string node) { // TODO multiple nodes gehen auch + public async Gee.List? request_all(XmppStream stream, Jid? jid, string node) throws IOError { // TODO multiple nodes gehen auch Iq.Stanza request_iq = new Iq.Stanza.get(new StanzaNode.build("pubsub", NS_URI).add_self_xmlns().put_node(new StanzaNode.build("items", NS_URI).put_attribute("node", node))); if (jid != null) request_iq.to = jid; @@ -72,7 +72,7 @@ namespace Xmpp.Xep.Pubsub { XmppStream stream, Jid? jid, string node_id, string? item_id, StanzaNode content, PublishOptions? publish_options = null, bool try_reconfiguring = true - ) { + ) throws IOError { StanzaNode pubsub_node = new StanzaNode.build("pubsub", NS_URI).add_self_xmlns(); StanzaNode publish_node = new StanzaNode.build("publish", NS_URI).put_attribute("node", node_id); pubsub_node.put_node(publish_node); @@ -142,7 +142,7 @@ namespace Xmpp.Xep.Pubsub { stream.get_module(Iq.Module.IDENTITY).send_iq(stream, iq, null); } - public async DataForms.DataForm? request_node_config(XmppStream stream, Jid? jid, string node_id) { + public async DataForms.DataForm? request_node_config(XmppStream stream, Jid? jid, string node_id) throws IOError { StanzaNode pubsub_node = new StanzaNode.build("pubsub", NS_URI_OWNER).add_self_xmlns(); StanzaNode publish_node = new StanzaNode.build("configure", NS_URI_OWNER).put_attribute("node", node_id); pubsub_node.put_node(publish_node); @@ -154,7 +154,7 @@ namespace Xmpp.Xep.Pubsub { return DataForms.DataForm.create_from_node(data_form_node); } - public async bool submit_node_config(XmppStream stream, DataForms.DataForm data_form, string node_id) { + public async bool submit_node_config(XmppStream stream, DataForms.DataForm data_form, string node_id) throws IOError { StanzaNode submit_node = data_form.get_submit_node(); StanzaNode pubsub_node = new StanzaNode.build("pubsub", Pubsub.NS_URI_OWNER).add_self_xmlns(); @@ -222,7 +222,7 @@ namespace Xmpp.Xep.Pubsub { } } - private async bool change_node_config(XmppStream stream, Jid jid, string node, PublishOptions publish_options) { + private async bool change_node_config(XmppStream stream, Jid jid, string node, PublishOptions publish_options) throws IOError { DataForms.DataForm? data_form = yield stream.get_module(Pubsub.Module.IDENTITY).request_node_config(stream, jid, node); if (data_form == null) return false; diff --git a/xmpp-vala/src/module/xep/0065_socks5_bytestreams.vala b/xmpp-vala/src/module/xep/0065_socks5_bytestreams.vala index 9cfb4afc..4bb46837 100644 --- a/xmpp-vala/src/module/xep/0065_socks5_bytestreams.vala +++ b/xmpp-vala/src/module/xep/0065_socks5_bytestreams.vala @@ -50,7 +50,7 @@ public class Module : XmppStreamModule, Iq.Handler { return get_local_ip_addresses_impl(); } - private async void query_availability(XmppStream stream) { + private async void query_availability(XmppStream stream) throws IOError { ServiceDiscovery.ItemsResult? items_result = yield stream.get_module(ServiceDiscovery.Module.IDENTITY).request_items(stream, stream.remote_name); if (items_result == null) return; diff --git a/xmpp-vala/src/module/xep/0077_in_band_registration.vala b/xmpp-vala/src/module/xep/0077_in_band_registration.vala index 12ed2f1c..0d2dae40 100644 --- a/xmpp-vala/src/module/xep/0077_in_band_registration.vala +++ b/xmpp-vala/src/module/xep/0077_in_band_registration.vala @@ -7,7 +7,7 @@ public const string NS_URI = "jabber:iq:register"; public class Module : XmppStreamNegotiationModule { public static ModuleIdentity IDENTITY = new ModuleIdentity(NS_URI, "0077_in_band_registration"); - public async Form? get_from_server(XmppStream stream, Jid jid) { + public async Form? get_from_server(XmppStream stream, Jid jid) throws IOError { StanzaNode query_node = new StanzaNode.build("query", NS_URI).add_self_xmlns(); Iq.Stanza request_form_iq = new Iq.Stanza.get(query_node) { to=jid }; request_form_iq.to = jid; @@ -16,7 +16,7 @@ public class Module : XmppStreamNegotiationModule { return new Form.from_node(stream, iq_result); } - public async string? submit_to_server(XmppStream stream, Jid jid, Form form) { + public async string? submit_to_server(XmppStream stream, Jid jid, Form form) throws IOError { StanzaNode query_node = new StanzaNode.build("query", NS_URI).add_self_xmlns(); query_node.put_node(form.get_submit_node()); Iq.Stanza iq = new Iq.Stanza.set(query_node) { to=jid }; @@ -30,7 +30,7 @@ public class Module : XmppStreamNegotiationModule { return null; } - public async ErrorStanza? change_password(XmppStream stream, Jid bare_jid, string new_pw) { + public async ErrorStanza? change_password(XmppStream stream, Jid bare_jid, string new_pw) throws IOError { StanzaNode pw_change_node = new StanzaNode.build("query", NS_URI).add_self_xmlns(); StanzaNode username_node = new StanzaNode.build("username", NS_URI); StanzaNode pw_node = new StanzaNode.build("password", NS_URI); 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 3237208e..8cd3cfb1 100644 --- a/xmpp-vala/src/module/xep/0082_date_time_profiles.vala +++ b/xmpp-vala/src/module/xep/0082_date_time_profiles.vala @@ -15,8 +15,8 @@ public TimeZone? parse_tzd(string tzd) { } else { return null; } - int h_offset = unprefixed.substring(0, 2).to_int(); - int m_offset = unprefixed.substring(3, 2).to_int(); + int h_offset = int.parse(unprefixed.substring(0, 2)); + int m_offset = int.parse(unprefixed.substring(3, 2)); int offset = h_offset * 60 * 60 + m_offset * 60; if (tzd.has_prefix("-")) { offset = -offset; diff --git a/xmpp-vala/src/module/xep/0084_user_avatars.vala b/xmpp-vala/src/module/xep/0084_user_avatars.vala index 680c8e78..76ad824b 100644 --- a/xmpp-vala/src/module/xep/0084_user_avatars.vala +++ b/xmpp-vala/src/module/xep/0084_user_avatars.vala @@ -25,7 +25,7 @@ namespace Xmpp.Xep.UserAvatars { stream.get_module(Pubsub.Module.IDENTITY).delete_node(stream, null, NS_URI_METADATA); } - public async Bytes? fetch_image(XmppStream stream, Jid jid, string hash) { + public async Bytes? fetch_image(XmppStream stream, Jid jid, string hash) throws IOError { Gee.List? items = yield stream.get_module(Pubsub.Module.IDENTITY).request_all(stream, jid, NS_URI_DATA); if (items == null || items.size == 0 || items[0].sub_nodes.size == 0) return null; diff --git a/xmpp-vala/src/module/xep/0166_jingle/component.vala b/xmpp-vala/src/module/xep/0166_jingle/component.vala index e30175d5..f08ffbce 100644 --- a/xmpp-vala/src/module/xep/0166_jingle/component.vala +++ b/xmpp-vala/src/module/xep/0166_jingle/component.vala @@ -41,7 +41,11 @@ namespace Xmpp.Xep.Jingle { assert(!this.stream.ready); promise.set_value(stream); if (terminated != null) { - yield stream.close_async(); + try { + yield stream.close_async(); + } catch (IOError e) { + warning("Failed closing stream provided for terminated connection: %s", e.message); + } } } @@ -53,7 +57,11 @@ namespace Xmpp.Xep.Jingle { if (terminated == null) { terminated = (reason_name ?? "") + " - " + (reason_text ?? "") + @"we terminated? $we_terminated"; if (stream.ready) { - yield stream.value.close_async(); + try { + yield stream.value.close_async(); + } catch (IOError e) { + warning("Failed closing stream when terminating connection: %s", e.message); + } } else { promise.set_exception(new IOError.FAILED("Jingle connection failed")); } diff --git a/xmpp-vala/src/module/xep/0166_jingle/content.vala b/xmpp-vala/src/module/xep/0166_jingle/content.vala index f6af1b17..07cadc36 100644 --- a/xmpp-vala/src/module/xep/0166_jingle/content.vala +++ b/xmpp-vala/src/module/xep/0166_jingle/content.vala @@ -67,7 +67,7 @@ public class Xmpp.Xep.Jingle.Content : Object { ContentType content_type, ContentParameters content_params, Transport transport, TransportParameters? transport_params, SecurityPrecondition? security_precondition, SecurityParameters? security_params, - Jid local_full_jid, Jid peer_full_jid) throws Error { + Jid local_full_jid, Jid peer_full_jid) { this.content_name = content_name; this.senders = senders; this.role = Role.RESPONDER; @@ -142,7 +142,7 @@ public class Xmpp.Xep.Jingle.Content : Object { this.content_params.accept(stream, session, this); } - internal void handle_accept(XmppStream stream, ContentNode content_node) { + internal void handle_accept(XmppStream stream, ContentNode content_node) throws IqError { this.transport_params.handle_transport_accept(content_node.transport); this.transport_params.create_transport_connection(stream, this); this.content_params.handle_accept(stream, this.session, this, content_node.description); @@ -157,7 +157,13 @@ public class Xmpp.Xep.Jingle.Content : Object { return; } tried_transport_methods.add(new_transport.ns_uri); - transport_params = new_transport.create_transport_parameters(stream, transport_params.components, local_full_jid, peer_full_jid); + try { + transport_params = new_transport.create_transport_parameters(stream, transport_params.components, local_full_jid, peer_full_jid); + } catch (Error e) { + session.terminate(ReasonElement.FAILED_TRANSPORT, null, e.message); + // TODO should we only terminate this content or really the whole session? + return; + } set_transport_params(transport_params); session.send_transport_replace(this, transport_params); state = State.REPLACING_TRANSPORT; diff --git a/xmpp-vala/src/module/xep/0166_jingle/jingle_flag.vala b/xmpp-vala/src/module/xep/0166_jingle/jingle_flag.vala index 9f0acd27..f451f4c9 100644 --- a/xmpp-vala/src/module/xep/0166_jingle/jingle_flag.vala +++ b/xmpp-vala/src/module/xep/0166_jingle/jingle_flag.vala @@ -24,7 +24,12 @@ public class Xmpp.Xep.Jingle.Flag : XmppStreamFlag { public async Session? get_session(string sid) { if (promises.has_key(sid)) { - return yield promises[sid].future.wait_async(); + try { + return yield promises[sid].future.wait_async(); + } catch (FutureError e) { + warning("Failed waiting for session: %s", e.message); + return null; + } } return sessions[sid]; } diff --git a/xmpp-vala/src/module/xep/0166_jingle/session.vala b/xmpp-vala/src/module/xep/0166_jingle/session.vala index 22e8e978..aa8d6656 100644 --- a/xmpp-vala/src/module/xep/0166_jingle/session.vala +++ b/xmpp-vala/src/module/xep/0166_jingle/session.vala @@ -222,7 +222,12 @@ public class Xmpp.Xep.Jingle.Session : Object { .put_node(content.transport_params.to_transport_stanza_node("content-add"))); Iq.Stanza iq = new Iq.Stanza.set(content_add_node) { to=peer_full_jid }; - yield stream.get_module(Iq.Module.IDENTITY).send_iq_async(stream, iq); + try { + yield stream.get_module(Iq.Module.IDENTITY).send_iq_async(stream, iq); + } catch (IOError e) { + warning("Error adding content: %s", e.message); + // FIXME: Probably need to clean up some state + } } private void handle_content_accept(ContentNode content_node) throws IqError { diff --git a/xmpp-vala/src/module/xep/0176_jingle_ice_udp/transport_parameters.vala b/xmpp-vala/src/module/xep/0176_jingle_ice_udp/transport_parameters.vala index 490cc125..53057d9e 100644 --- a/xmpp-vala/src/module/xep/0176_jingle_ice_udp/transport_parameters.vala +++ b/xmpp-vala/src/module/xep/0176_jingle_ice_udp/transport_parameters.vala @@ -39,7 +39,11 @@ public abstract class Xmpp.Xep.JingleIceUdp.IceUdpTransportParameters : Jingle.T remote_pwd = node.get_attribute("pwd"); remote_ufrag = node.get_attribute("ufrag"); foreach (StanzaNode candidateNode in node.get_subnodes("candidate")) { - remote_candidates.add(Candidate.parse(candidateNode)); + try { + remote_candidates.add(Candidate.parse(candidateNode)); + } catch (Jingle.IqError e) { + warning("Ignoring invalid candidate %s: %s", candidateNode.to_string(), e.message); + } } StanzaNode? fingerprint_node = node.get_subnode("fingerprint", DTLS_NS_URI); diff --git a/xmpp-vala/src/module/xep/0191_blocking_command.vala b/xmpp-vala/src/module/xep/0191_blocking_command.vala index 9e5ede78..c037c480 100644 --- a/xmpp-vala/src/module/xep/0191_blocking_command.vala +++ b/xmpp-vala/src/module/xep/0191_blocking_command.vala @@ -88,11 +88,15 @@ public class Module : XmppStreamModule, Iq.Handler { bool has_feature = yield stream.get_module(ServiceDiscovery.Module.IDENTITY).has_entity_feature(stream, stream.remote_name, NS_URI); if (has_feature) { stream.add_flag(new Flag()); - stream.get_flag(Flag.IDENTITY).blocklist = yield get_blocklist(stream); + try { + stream.get_flag(Flag.IDENTITY).blocklist = yield get_blocklist(stream); + } catch (IOError e) { + warning("Failed to retrieve blocklist: %s", e.message); + } } } - private async Gee.List get_blocklist(XmppStream stream) { + private async Gee.List get_blocklist(XmppStream stream) throws IOError { StanzaNode blocklist_node = new StanzaNode.build("blocklist", NS_URI).add_self_xmlns(); Iq.Stanza iq = new Iq.Stanza.get(blocklist_node); diff --git a/xmpp-vala/src/module/xep/0199_ping.vala b/xmpp-vala/src/module/xep/0199_ping.vala index 18fa538c..7bc234bc 100644 --- a/xmpp-vala/src/module/xep/0199_ping.vala +++ b/xmpp-vala/src/module/xep/0199_ping.vala @@ -6,7 +6,7 @@ namespace Xmpp.Xep.Ping { public class Module : XmppStreamModule, Iq.Handler { public static ModuleIdentity IDENTITY = new ModuleIdentity(NS_URI, "0199_ping"); - public async Iq.Stanza send_ping(XmppStream stream, Jid jid) { + public async Iq.Stanza send_ping(XmppStream stream, Jid jid) throws IOError { StanzaNode ping_node = new StanzaNode.build("ping", NS_URI).add_self_xmlns(); Iq.Stanza iq = new Iq.Stanza.get(ping_node) { to=jid }; return yield stream.get_module(Iq.Module.IDENTITY).send_iq_async(stream, iq, Priority.HIGH); diff --git a/xmpp-vala/src/module/xep/0215_external_service_discovery.vala b/xmpp-vala/src/module/xep/0215_external_service_discovery.vala index 5bad71cd..67f67c05 100644 --- a/xmpp-vala/src/module/xep/0215_external_service_discovery.vala +++ b/xmpp-vala/src/module/xep/0215_external_service_discovery.vala @@ -4,7 +4,7 @@ namespace Xmpp.Xep.ExternalServiceDiscovery { private const string NS_URI = "urn:xmpp:extdisco:2"; - public static async Gee.List request_services(XmppStream stream) { + public static async Gee.List request_services(XmppStream stream) throws IOError { Iq.Stanza request_iq = new Iq.Stanza.get((new StanzaNode.build("services", NS_URI)).add_self_xmlns()) { to=stream.remote_name }; Iq.Stanza response_iq = yield stream.get_module(Iq.Module.IDENTITY).send_iq_async(stream, request_iq); diff --git a/xmpp-vala/src/module/xep/0234_jingle_file_transfer.vala b/xmpp-vala/src/module/xep/0234_jingle_file_transfer.vala index f85049ac..43edefad 100644 --- a/xmpp-vala/src/module/xep/0234_jingle_file_transfer.vala +++ b/xmpp-vala/src/module/xep/0234_jingle_file_transfer.vala @@ -42,7 +42,7 @@ public class Module : Jingle.ContentType, XmppStreamModule { return yield stream.get_module(Jingle.Module.IDENTITY).is_available(stream, required_transport_type, required_components, full_jid); } - public async void offer_file_stream(XmppStream stream, Jid receiver_full_jid, InputStream input_stream, string basename, int64 size, string? precondition_name = null, Object? precondition_options = null) throws Jingle.Error { + public async void offer_file_stream(XmppStream stream, Jid receiver_full_jid, InputStream input_stream, string basename, int64 size, string? precondition_name = null, Object? precondition_options = null) throws Jingle.Error, Jingle.IqError { StanzaNode file_node; StanzaNode description = new StanzaNode.build("description", NS_URI) .add_self_xmlns() diff --git a/xmpp-vala/src/module/xep/0260_jingle_socks5_bytestreams.vala b/xmpp-vala/src/module/xep/0260_jingle_socks5_bytestreams.vala index 852cc7eb..7f44bda2 100644 --- a/xmpp-vala/src/module/xep/0260_jingle_socks5_bytestreams.vala +++ b/xmpp-vala/src/module/xep/0260_jingle_socks5_bytestreams.vala @@ -328,9 +328,13 @@ class LocalListener { } } catch (Error e) { warning("Jingle SOCKS5 bytestream establish error cid %s: %s", cid, e.message); - // Send a "general SOCKS server failure" (0x01) - yield conn.output_stream.write_all_async({0x05, 0x01}, GLib.Priority.DEFAULT, null, out written); - yield conn.output_stream.close_async(); + try { + // Send a "general SOCKS server failure" (0x01) + yield conn.output_stream.write_all_async({0x05, 0x01}, GLib.Priority.DEFAULT, null, out written); + yield conn.output_stream.close_async(); + } catch (Error sub) { + // Ignoring + } } } diff --git a/xmpp-vala/src/module/xep/0272_muji.vala b/xmpp-vala/src/module/xep/0272_muji.vala index 8226da10..7fd0da19 100644 --- a/xmpp-vala/src/module/xep/0272_muji.vala +++ b/xmpp-vala/src/module/xep/0272_muji.vala @@ -86,7 +86,12 @@ namespace Xmpp.Xep.Muji { GroupCall group_call = stream.get_flag(Flag.IDENTITY).calls[muc_jid]; group_call.waiting_for_finish_prepares[promise] = preparing_peers; - return yield promise.future.wait_async(); + try { + return yield promise.future.wait_async(); + } catch (FutureError e) { + warning("Failed waiting for preparing peers: %s", e.message); + return new ArrayList(); + } } private async void compute_payload_intersection(XmppStream stream, GroupCall group_call, string media) { diff --git a/xmpp-vala/src/module/xep/0280_message_carbons.vala b/xmpp-vala/src/module/xep/0280_message_carbons.vala index b396ee54..f8008bfe 100644 --- a/xmpp-vala/src/module/xep/0280_message_carbons.vala +++ b/xmpp-vala/src/module/xep/0280_message_carbons.vala @@ -10,13 +10,21 @@ public class Module : XmppStreamModule { public async void enable(XmppStream stream) { if (stream.get_flag(Flag.IDENTITY) == null) { Iq.Stanza iq = new Iq.Stanza.set(new StanzaNode.build("enable", NS_URI).add_self_xmlns()); - yield stream.get_module(Iq.Module.IDENTITY).send_iq_async(stream, iq); + try { + yield stream.get_module(Iq.Module.IDENTITY).send_iq_async(stream, iq); + } catch (IOError e) { + warning("Failed enabling carbons: %s", e.message); + } } } public async void disable(XmppStream stream) { Iq.Stanza iq = new Iq.Stanza.set(new StanzaNode.build("disable", NS_URI).add_self_xmlns()); - yield stream.get_module(Iq.Module.IDENTITY).send_iq_async(stream, iq); + try { + yield stream.get_module(Iq.Module.IDENTITY).send_iq_async(stream, iq); + } catch (IOError e) { + warning("Failed enabling carbons: %s", e.message); + } } public override void attach(XmppStream stream) { diff --git a/xmpp-vala/src/module/xep/0313_2_message_archive_management.vala b/xmpp-vala/src/module/xep/0313_2_message_archive_management.vala index 298a1c61..17b93d55 100644 --- a/xmpp-vala/src/module/xep/0313_2_message_archive_management.vala +++ b/xmpp-vala/src/module/xep/0313_2_message_archive_management.vala @@ -61,7 +61,7 @@ namespace Xmpp.MessageArchiveManagement.V2 { return MessageArchiveManagement.create_base_query(stream, mam_params.query_id, fields); } - public async QueryResult query_archive(XmppStream stream, MamQueryParams mam_params, Cancellable? cancellable = null) { + public async QueryResult query_archive(XmppStream stream, MamQueryParams mam_params, Cancellable? cancellable = null) throws IOError { var query_node = create_base_query(stream, mam_params); if (!mam_params.use_ns2_extended) { query_node.put_node(ResultSetManagement.create_set_rsm_node_before(mam_params.end_id)); @@ -70,7 +70,7 @@ namespace Xmpp.MessageArchiveManagement.V2 { return yield MessageArchiveManagement.query_archive(stream, mam_params.mam_server, query_node, cancellable); } - public async QueryResult page_through_results(XmppStream stream, MamQueryParams mam_params, QueryResult prev_result, Cancellable? cancellable = null) { + public async QueryResult page_through_results(XmppStream stream, MamQueryParams mam_params, QueryResult prev_result, Cancellable? cancellable = null) throws IOError { var query_node = create_base_query(stream, mam_params); query_node.put_node(ResultSetManagement.create_set_rsm_node_before(prev_result.first)); diff --git a/xmpp-vala/src/module/xep/0313_message_archive_management.vala b/xmpp-vala/src/module/xep/0313_message_archive_management.vala index a9149d4e..f054a047 100644 --- a/xmpp-vala/src/module/xep/0313_message_archive_management.vala +++ b/xmpp-vala/src/module/xep/0313_message_archive_management.vala @@ -57,7 +57,7 @@ public class Module : XmppStreamModule { return query_node; } - internal async QueryResult query_archive(XmppStream stream, Jid? mam_server, StanzaNode query_node, Cancellable? cancellable = null) { + internal async QueryResult query_archive(XmppStream stream, Jid? mam_server, StanzaNode query_node, Cancellable? cancellable = null) throws IOError { var res = new QueryResult(); Flag flag = Flag.get_flag(stream); diff --git a/xmpp-vala/src/module/xep/0392_consistent_color/hsluv.vala b/xmpp-vala/src/module/xep/0392_consistent_color/hsluv.vala index b45d84de..10aa5d9f 100644 --- a/xmpp-vala/src/module/xep/0392_consistent_color/hsluv.vala +++ b/xmpp-vala/src/module/xep/0392_consistent_color/hsluv.vala @@ -340,9 +340,9 @@ internal string rgb_to_hex(double[] tuple) { internal double[] hex_to_tgb(string hex) { return new double[] { - hex.substring(1, 2).to_long(null, 16) / 255.0, - hex.substring(3, 2).to_long(null, 16) / 255.0, - hex.substring(5, 2).to_long(null, 16) / 255.0 + long.parse(hex.substring(1, 2), 16) / 255.0, + long.parse(hex.substring(3, 2), 16) / 255.0, + long.parse(hex.substring(5, 2), 16) / 255.0 }; } diff --git a/xmpp-vala/src/module/xep/0402_bookmarks2.vala b/xmpp-vala/src/module/xep/0402_bookmarks2.vala index 938af0e9..3a7e5211 100644 --- a/xmpp-vala/src/module/xep/0402_bookmarks2.vala +++ b/xmpp-vala/src/module/xep/0402_bookmarks2.vala @@ -8,7 +8,7 @@ public const string NS_URI_COMPAT = NS_URI + "#compat"; public class Module : BookmarksProvider, XmppStreamModule { public static ModuleIdentity IDENTITY = new ModuleIdentity(NS_URI, "0402_bookmarks2"); - public async Set? get_conferences(XmppStream stream) { + public async Set? get_conferences(XmppStream stream) throws IOError { HashMap? hm = null; Flag? flag = stream.get_flag(Flag.IDENTITY); @@ -35,7 +35,7 @@ public class Module : BookmarksProvider, XmppStreamModule { return ret; } - public async void add_conference(XmppStream stream, Conference conference) { + public async void add_conference(XmppStream stream, Conference conference) throws IOError { StanzaNode conference_node = new StanzaNode.build("conference", NS_URI).add_self_xmlns() .put_attribute("autojoin", conference.autojoin.to_string()); if (conference.name != null) { @@ -56,7 +56,7 @@ public class Module : BookmarksProvider, XmppStreamModule { yield stream.get_module(Pubsub.Module.IDENTITY).publish(stream, stream.get_flag(Bind.Flag.IDENTITY).my_jid.bare_jid, NS_URI, conference.jid.to_string(), conference_node, publish_options); } - public async void replace_conference(XmppStream stream, Jid muc_jid, Conference modified_conference) { + public async void replace_conference(XmppStream stream, Jid muc_jid, Conference modified_conference) throws IOError { yield add_conference(stream, modified_conference); } diff --git a/xmpp-vala/src/module/xep/0461_replies.vala b/xmpp-vala/src/module/xep/0461_replies.vala index af84aa3d..23849d69 100644 --- a/xmpp-vala/src/module/xep/0461_replies.vala +++ b/xmpp-vala/src/module/xep/0461_replies.vala @@ -36,6 +36,5 @@ namespace Xmpp.Xep.Replies { } catch (InvalidJidError e) { return null; } - return null; } } \ No newline at end of file diff --git a/xmpp-vala/tests/color.vala b/xmpp-vala/tests/color.vala index ded67d53..a00710c5 100644 --- a/xmpp-vala/tests/color.vala +++ b/xmpp-vala/tests/color.vala @@ -39,9 +39,9 @@ class ColorTest : Gee.TestCase { public void test_rgb_to_angle() { string[] colors = {"e57373", "f06292", "ba68c8", "9575cd", "7986cb", "64b5f6", "4fc3f7", "4dd0e1", "4db6ac", "81c784", "aed581", "dce775", "fff176", "ffd54f", "ffb74d", "ff8a65"}; foreach(string hex_color in colors) { - uint8 r = (uint8) ((double) hex_color.substring(0, 2).to_long(null, 16)); - uint8 g = (uint8) ((double) hex_color.substring(2, 2).to_long(null, 16)); - uint8 b = (uint8) ((double) hex_color.substring(4, 2).to_long(null, 16)); + uint8 r = (uint8) ((double) long.parse(hex_color.substring(0, 2), 16)); + uint8 g = (uint8) ((double) long.parse(hex_color.substring(2, 2), 16)); + uint8 b = (uint8) ((double) long.parse(hex_color.substring(4, 2), 16)); //print(@"$hex_color, $r, $g, $b, $(ConsistentColor.rgb_to_angle(r, g, b))\n"); } } diff --git a/xmpp-vala/tests/stanza.vala b/xmpp-vala/tests/stanza.vala index 42e19f9a..b573995f 100644 --- a/xmpp-vala/tests/stanza.vala +++ b/xmpp-vala/tests/stanza.vala @@ -21,11 +21,15 @@ class StanzaTest : Gee.TestCase { .put_node(new StanzaNode.build("testbb", "ns3_uri") .add_self_xmlns()); - var xml1 = node1.to_xml(); - var node2 = yield new StanzaReader.for_string(xml1).read_node(); - fail_if_not(node1.equals(node2)); - fail_if_not_eq_str(node1.to_string(), node2.to_string()); - cb(); + try { + var xml1 = node1.to_xml(); + var node2 = yield new StanzaReader.for_string(xml1).read_node(); + fail_if_not(node1.equals(node2)); + fail_if_not_eq_str(node1.to_string(), node2.to_string()); + cb(); + } catch (GLib.Error e) { + cb(e); + } } private async void test_typical_stream(Gee.TestFinishedCallback cb) { @@ -55,12 +59,16 @@ class StanzaTest : Gee.TestCase { .put_node(new StanzaNode.build("body") .put_node(new StanzaNode.text(" I'll send a friar with speed, to Mantua, with my letters to thy lord."))); - var reader = new StanzaReader.for_string(stream); - fail_if_not_eq_node(root_node_cmp, yield reader.read_root_node()); - fail_if_not_eq_node(node_cmp, yield reader.read_node()); - yield reader.read_node(); - yield fail_if_not_end_of_stream(reader); - cb(); + try { + var reader = new StanzaReader.for_string(stream); + fail_if_not_eq_node(root_node_cmp, yield reader.read_root_node()); + fail_if_not_eq_node(node_cmp, yield reader.read_node()); + yield reader.read_node(); + yield fail_if_not_end_of_stream(reader); + cb(); + } catch (GLib.Error e) { + cb(e); + } } private async void fail_if_not_end_of_stream(StanzaReader reader) { @@ -105,13 +113,17 @@ class StanzaTest : Gee.TestCase { .put_node(new StanzaNode.build("required", "urn:ietf:params:xml:ns:xmpp-bind"))); var node2_cmp = new StanzaNode.build("r", "http://jabber.org/protocol/ack"); - var reader = new StanzaReader.for_string(stream); - fail_if_not_eq_node(root_node_cmp, yield reader.read_root_node()); - fail_if_not_eq_node(node_cmp, yield reader.read_node()); - fail_if_not_eq_node(node2_cmp, yield reader.read_node()); - yield reader.read_node(); - yield fail_if_not_end_of_stream(reader); - cb(); + try { + var reader = new StanzaReader.for_string(stream); + fail_if_not_eq_node(root_node_cmp, yield reader.read_root_node()); + fail_if_not_eq_node(node_cmp, yield reader.read_node()); + fail_if_not_eq_node(node2_cmp, yield reader.read_node()); + yield reader.read_node(); + yield fail_if_not_end_of_stream(reader); + cb(); + } catch (GLib.Error e) { + cb(e); + } } private void test_encoded_val() { diff --git a/xmpp-vala/tests/testcase.vala b/xmpp-vala/tests/testcase.vala index 178b6353..10da78f6 100644 --- a/xmpp-vala/tests/testcase.vala +++ b/xmpp-vala/tests/testcase.vala @@ -62,7 +62,7 @@ public abstract class Gee.TestCase : Object { namespace Gee { public delegate void TestMethod (); - public delegate void TestFinishedCallback (); + public delegate void TestFinishedCallback (GLib.Error? error = null); public delegate void AsyncTestMethod (TestFinishedCallback cb); } @@ -128,8 +128,15 @@ private class Gee.TestAsyncAdaptor : Object, TestAdaptor { main_loop.run (); } - public void finish () { - Idle.add (() => { main_loop.quit (); return false; }); + public void finish (GLib.Error? error = null) { + Idle.add(() => { + main_loop.quit(); + if (error != null) { + Test.fail(); + Test.message("Error: %s", error.message); + } + return false; + }); } public bool finish_timeout () { -- cgit v1.3 From ef925d1897d581cba0c61370f3aa9d80dac94b7c Mon Sep 17 00:00:00 2001 From: Marvin W Date: Fri, 23 Oct 2026 16:49:15 +0200 Subject: avatar: Prevent reloading the same avatar on minor state changes --- libdino/src/service/avatar_manager.vala | 12 ++-- main/src/ui/widgets/avatar_picture.vala | 107 ++++++++++++++++++++++++-------- 2 files changed, 87 insertions(+), 32 deletions(-) (limited to 'main/src/ui') diff --git a/libdino/src/service/avatar_manager.vala b/libdino/src/service/avatar_manager.vala index 7f69f508..d079b70d 100644 --- a/libdino/src/service/avatar_manager.vala +++ b/libdino/src/service/avatar_manager.vala @@ -157,19 +157,19 @@ 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(account, entry.key, entry.value, false); } foreach (var entry in get_avatar_hashes(account, Source.VCARD).entries) { - on_vcard_avatar_received(account, entry.key, entry.value); + on_vcard_avatar_received(account, entry.key, entry.value, false); } } - private void on_user_avatar_received(Account account, Jid jid_, string id) { + private void on_user_avatar_received(Account account, Jid jid_, string id, bool is_live = true) { Jid jid = jid_.bare_jid; if (!user_avatars.has_key(jid) || user_avatars[jid] != id) { user_avatars[jid] = id; - set_avatar_hash(account, jid, id, Source.USER_AVATARS); + if (is_live) set_avatar_hash(account, jid, id, Source.USER_AVATARS); } received_avatar(jid, account); } @@ -181,13 +181,13 @@ public class AvatarManager : StreamInteractionModule, Object { received_avatar(jid, account); } - private void on_vcard_avatar_received(Account account, Jid jid_, string id) { + private void on_vcard_avatar_received(Account account, Jid jid_, string id, bool is_live = true) { bool is_gc = stream_interactor.get_module(MucManager.IDENTITY).might_be_groupchat(jid_.bare_jid, account); Jid jid = is_gc ? jid_ : jid_.bare_jid; if (!vcard_avatars.has_key(jid) || vcard_avatars[jid] != id) { vcard_avatars[jid] = id; - if (jid.is_bare()) { // don't save MUC occupant avatars + if (is_live && jid.is_bare()) { // don't save MUC occupant avatars set_avatar_hash(account, jid, id, Source.VCARD); } } diff --git a/main/src/ui/widgets/avatar_picture.vala b/main/src/ui/widgets/avatar_picture.vala index 12c635aa..d22f9cfe 100644 --- a/main/src/ui/widgets/avatar_picture.vala +++ b/main/src/ui/widgets/avatar_picture.vala @@ -5,7 +5,20 @@ using Xmpp; public class Dino.Ui.ViewModel.AvatarPictureTileModel : Object { public string display_text { get; set; } public Gdk.RGBA background_color { get; set; } - public File? image_file { get; set; } + + private File? _image_file; + [CCode (notify = false)] + public File? image_file { + get { + return _image_file; + } + set { + if (_image_file == value) return; + if (_image_file != null && value != null && _image_file.equal(value)) return; + _image_file = value; + notify_property("image-file"); + } + } } public class Dino.Ui.ViewModel.AvatarPictureModel : Object { @@ -23,8 +36,18 @@ public class Dino.Ui.ViewModel.ConversationParticipantAvatarPictureTileModel : A public ConversationParticipantAvatarPictureTileModel(StreamInteractor stream_interactor, Conversation conversation, Jid jid) { this.stream_interactor = stream_interactor; + + set_conversation_participant(conversation, jid); + + stream_interactor.get_module(RosterManager.IDENTITY).updated_roster_item.connect(on_roster_updated); + avatar_manager.received_avatar.connect(on_received_avatar); + avatar_manager.fetched_avatar.connect(on_received_avatar); + } + + public void set_conversation_participant(Conversation conversation, Jid jid) { this.conversation = conversation; this.primary_avatar_jid = jid; + this.secondary_avatar_jid = null; this.display_name_jid = jid; string color_id = jid.to_string(); @@ -43,14 +66,11 @@ public class Dino.Ui.ViewModel.ConversationParticipantAvatarPictureTileModel : A } string display = Util.get_participant_display_name(stream_interactor, conversation, display_name_jid); display_text = display.get_char(0).toupper().to_string(); - stream_interactor.get_module(RosterManager.IDENTITY).updated_roster_item.connect(on_roster_updated); float[] rgbf = color_id != null ? Xep.ConsistentColor.string_to_rgbf(color_id) : new float[] {0.5f, 0.5f, 0.5f}; background_color = Gdk.RGBA() { red = rgbf[0], green = rgbf[1], blue = rgbf[2], alpha = 1.0f}; update_image_file(); - avatar_manager.received_avatar.connect(on_received_avatar); - avatar_manager.fetched_avatar.connect(on_received_avatar); } private void update_image_file() { @@ -140,55 +160,76 @@ public class Dino.Ui.ViewModel.CompatAvatarPictureModel : AvatarPictureModel { private void on_room_updated(Account account, Jid room) { if (conversation != null && account.equals(conversation.account) && conversation.counterpart.equals_bare(room)) { - reset(); - set_conversation(conversation); + update_all(); } } private void on_received_avatar(Jid jid, Account account) { + // Only update group chat avatar when the respective jid avatar is actually used in it + if (conversation == null || conversation.type_ == Conversation.Type.GROUPCHAT && !jid.is_bare()) return; on_room_updated(account, jid); } - public void reset() { - (tiles as GLib.ListStore).remove_all(); - } - public CompatAvatarPictureModel set_conversation(Conversation conversation) { if (stream_interactor == null) { critical("set_conversation() used on CompatAvatarPictureModel without stream_interactor"); return this; } this.conversation = conversation; + update_all(); + return this; + } + + private void update_all() { + uint idx = 0; if (conversation.type_ == Conversation.Type.GROUPCHAT) { if (avatar_manager.has_avatar(conversation.account, conversation.counterpart)) { - add_internal("#", conversation.counterpart.to_string(), avatar_manager.get_avatar_file(conversation.account, conversation.counterpart)); + set_internal(idx++, "#", conversation.counterpart.to_string(), avatar_manager.get_avatar_file(conversation.account, conversation.counterpart)); } else { Gee.List? occupants = muc_manager.get_other_offline_members(conversation.counterpart, conversation.account); if (occupants != null && !occupants.is_empty && muc_manager.is_private_room(conversation.account, conversation.counterpart)) { int count = occupants.size > 4 ? 3 : occupants.size; for (int i = 0; i < count; i++) { - add_participant(conversation, occupants[i]); + set_participant(idx++, conversation, occupants[i]); } if (occupants.size > 4) { - add_internal("+"); + set_internal(idx++, "+"); } } else { - add_internal("#", conversation.counterpart.to_string()); + set_internal(idx++, "#", conversation.counterpart.to_string()); } } } else { - add_participant(conversation, conversation.counterpart); + set_participant(idx++, conversation, conversation.counterpart); + } + + GLib.ListStore store = tiles as GLib.ListStore; + if (store.get_n_items() >= idx) { + store.splice(idx, store.get_n_items() - idx, {}); } - return this; } public CompatAvatarPictureModel add_participant(Conversation conversation, Jid jid) { + set_participant(tiles.get_n_items(), conversation, jid); + return this; + } + + public AvatarPictureTileModel set_participant(uint idx, Conversation conversation, Jid jid) { if (stream_interactor == null) { - critical("add_participant() used on CompatAvatarPictureModel without stream_interactor"); - return this; + error("set_participant() used on CompatAvatarPictureModel without stream_interactor"); } - (tiles as GLib.ListStore).append(new ConversationParticipantAvatarPictureTileModel(stream_interactor, conversation, jid)); - return this; + GLib.ListStore store = tiles as GLib.ListStore; + while (idx >= store.get_n_items()) { + store.append(new ViewModel.AvatarPictureTileModel()); + } + var model = store.get_item(idx) as ConversationParticipantAvatarPictureTileModel; + if (model == null) { + model = new ConversationParticipantAvatarPictureTileModel(stream_interactor, conversation, jid); + store.splice(idx, 1, {model}); + } else { + model.set_conversation_participant(conversation, jid); + } + return model; } public CompatAvatarPictureModel add(string display, string? color_id = null, File? image_file = null) { @@ -197,14 +238,28 @@ public class Dino.Ui.ViewModel.CompatAvatarPictureModel : AvatarPictureModel { } private AvatarPictureTileModel add_internal(string display, string? color_id = null, File? image_file = null) { + return set_internal(tiles.get_n_items(), display, color_id, image_file); + } + + private AvatarPictureTileModel set_internal(uint idx, string display, string? color_id = null, File? image_file = null) { GLib.ListStore store = tiles as GLib.ListStore; float[] rgbf = color_id != null ? Xep.ConsistentColor.string_to_rgbf(color_id) : new float[] {0.5f, 0.5f, 0.5f}; - var model = new ViewModel.AvatarPictureTileModel() { - display_text = display.get_char(0).toupper().to_string(), - background_color = Gdk.RGBA() { red = rgbf[0], green = rgbf[1], blue = rgbf[2], alpha = 1.0f}, - image_file = image_file - }; - store.append(model); + while (idx >= store.get_n_items()) { + store.append(new ViewModel.AvatarPictureTileModel()); + } + var model = store.get_item(idx) as ViewModel.AvatarPictureTileModel; + if (model == null) { + model = new ViewModel.AvatarPictureTileModel() { + display_text = display.get_char(0).toupper().to_string(), + background_color = Gdk.RGBA() { red = rgbf[0], green = rgbf[1], blue = rgbf[2], alpha = 1.0f}, + image_file = image_file + }; + store.splice(idx, 1, {model}); + } else { + model.display_text = display.get_char(0).toupper().to_string(); + model.background_color = Gdk.RGBA() { red = rgbf[0], green = rgbf[1], blue = rgbf[2], alpha = 1.0f}; + model.image_file = image_file; + } return model; } } -- cgit v1.3 From 873f980faad23135edc1028de6c7f51c16df8ac3 Mon Sep 17 00:00:00 2001 From: Marvin W Date: Fri, 23 Oct 2026 16:56:45 +0200 Subject: main: Improve conversation selector performance on startup --- main/src/ui/conversation_selector/conversation_selector.vala | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) (limited to 'main/src/ui') diff --git a/main/src/ui/conversation_selector/conversation_selector.vala b/main/src/ui/conversation_selector/conversation_selector.vala index 5d88c540..70e192b7 100644 --- a/main/src/ui/conversation_selector/conversation_selector.vala +++ b/main/src/ui/conversation_selector/conversation_selector.vala @@ -22,7 +22,7 @@ public class ConversationSelector : Widget { list_box.add_css_class("navigation-sidebar"); this.layout_manager = new BinLayout(); - stream_interactor.get_module(ConversationManager.IDENTITY).conversation_activated.connect(add_conversation); + stream_interactor.get_module(ConversationManager.IDENTITY).conversation_activated.connect(on_conversation_activated); stream_interactor.get_module(ConversationManager.IDENTITY).conversation_deactivated.connect(remove_conversation); stream_interactor.get_module(ContentItemStore.IDENTITY).new_item.connect(on_content_item_received); Timeout.add_seconds(60, () => { @@ -33,6 +33,7 @@ public class ConversationSelector : Widget { foreach (Conversation conversation in stream_interactor.get_module(ConversationManager.IDENTITY).get_active_conversations()) { add_conversation(conversation); } + list_box.invalidate_sort(); return this; } @@ -62,6 +63,7 @@ public class ConversationSelector : Widget { public void on_conversation_selected(Conversation conversation) { if (!rows.has_key(conversation)) { add_conversation(conversation); + list_box.invalidate_sort(); } list_box.select_row(rows[conversation]); } @@ -72,6 +74,11 @@ public class ConversationSelector : Widget { } } + private void on_conversation_activated(Conversation conversation) { + add_conversation(conversation); + list_box.invalidate_sort(); + } + private void add_conversation(Conversation conversation) { ConversationSelectorRow row; if (!rows.has_key(conversation)) { @@ -101,7 +108,6 @@ public class ConversationSelector : Widget { }); row.add_controller(drop_motion_controller); } - list_box.invalidate_sort(); } private void select_fallback_conversation(Conversation conversation) { -- cgit v1.3