From 210cc61ee4191465805a770881235c677041f929 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Frings-F=C3=BCrst?= Date: Sun, 8 Mar 2026 11:11:07 +0100 Subject: New upstream version 0.32.15 --- src/DirectoryMonitor.vala | 6 +++--- src/Photo.vala | 2 +- src/Resources.vala | 6 +++--- src/Tombstone.vala | 5 ++++- src/db/DatabaseTable.vala | 2 +- src/db/Db.vala | 8 ++++++-- src/db/VideoTable.vala | 2 ++ src/dialogs/ExportDialog.vala | 12 +++++++----- src/main.vala | 19 ++++++++++++++----- src/meson.build | 2 +- src/photos/AvifSupport.vala | 25 ++++++++++++++++++++----- src/photos/HeifSupport.vala | 2 +- src/photos/JfifSupport.vala | 6 +++++- src/photos/PhotoFileFormat.vala | 1 + src/photos/PhotoMetadata.vala | 4 ++++ src/plugins/Plugins.vala | 10 ++-------- src/plugins/SpitInterfaces.vala | 2 +- src/util/image.vala | 38 ++++++++++++++++++++++++++++++++++---- src/util/string.vala | 2 +- 19 files changed, 111 insertions(+), 43 deletions(-) (limited to 'src') diff --git a/src/DirectoryMonitor.vala b/src/DirectoryMonitor.vala index 19992dd..87d9a2e 100644 --- a/src/DirectoryMonitor.vala +++ b/src/DirectoryMonitor.vala @@ -871,7 +871,7 @@ public class DirectoryMonitor : Object { } } - if (local_dir_info.get_is_hidden()) { + if (local_dir_info.has_attribute("standard::is-hidden") && local_dir_info.get_is_hidden()) { warning("Ignoring hidden directory %s", dir.get_path()); explore_directory_completed(in_discovery); @@ -918,7 +918,7 @@ public class DirectoryMonitor : Object { dir.get_uri()); } // we don't deal with hidden files or directories - if (info.get_is_hidden()) { + if (info.has_attribute("standard::is-hidden") && info.get_is_hidden()) { warning("Skipping hidden file/directory %s", dir.get_child(info.get_name()).get_path()); @@ -1439,7 +1439,7 @@ public class DirectoryMonitor : Object { // Returns true if the file is not a symlink or if symlinks are supported for the file type, // false otherwise. If an unsupported file type, returns false. public static bool is_file_symlink_supported(FileInfo info) { - if (!info.get_is_symlink()) + if (info.has_attribute("standard::is-symlink") && !info.get_is_symlink()) return true; FType ftype = get_ftype(info); diff --git a/src/Photo.vala b/src/Photo.vala index 2b90361..1cab22f 100644 --- a/src/Photo.vala +++ b/src/Photo.vala @@ -3723,7 +3723,7 @@ public abstract class Photo : PhotoSource, Dateable, Positionable { if (metadata == null) metadata = export_format.create_metadata(); - if (!export_format.can_write()) + if (!export_format.can_write_image()) export_format = PhotoFileFormat.get_system_default_format(); PhotoFileWriter writer = export_format.create_writer(dest_file.get_path()); diff --git a/src/Resources.vala b/src/Resources.vala index 0bd8512..a99a210 100644 --- a/src/Resources.vala +++ b/src/Resources.vala @@ -15,9 +15,9 @@ namespace Resources { public const string COPYRIGHT = _("Copyright 2016 Software Freedom Conservancy Inc."); public const string APP_GETTEXT_PACKAGE = GETTEXT_PACKAGE; - public const string HOME_URL = "https://wiki.gnome.org/Apps/Shotwell"; - public const string FAQ_URL = "https://wiki.gnome.org/Apps/Shotwell/FAQ"; - public const string BUG_DB_URL = "https://wiki.gnome.org/Apps/Shotwell/ReportingABug"; + public const string HOME_URL = "https://shotwell-project.org"; + public const string FAQ_URL = "https://gitlab.gnome.org/GNOME/shotwell/-/wikis/Frequently-Asked-Questions"; + public const string BUG_DB_URL = "https://gitlab.gnome.org/GNOME/shotwell/issues"; public const string DIR_PATTERN_URI_SYSWIDE = "help:shotwell/other-files"; private const string LIB = _LIB; diff --git a/src/Tombstone.vala b/src/Tombstone.vala index 23cd984..2cae0c0 100644 --- a/src/Tombstone.vala +++ b/src/Tombstone.vala @@ -112,7 +112,10 @@ public class TombstoneSourceCollection : DatabaseSourceCollection { private async void async_scan(DirectoryMonitor? monitor, Cancellable? cancellable) { // search through all tombstones for missing files, which indicate the tombstone can go away Marker marker = start_marking(); - foreach (DataObject object in get_all()) { + + // There is an issue with modifying this list while this loop here is iterating it, source unknown + // Getting a copy of the list to work-around this (https://gitlab.gnome.org/GNOME/shotwell/-/issues/181) + foreach (DataObject object in get_dataset_copy().get_all()) { Tombstone tombstone = (Tombstone) object; File file = tombstone.get_file(); diff --git a/src/db/DatabaseTable.vala b/src/db/DatabaseTable.vala index be45e5e..5d84df2 100644 --- a/src/db/DatabaseTable.vala +++ b/src/db/DatabaseTable.vala @@ -57,7 +57,7 @@ public abstract class DatabaseTable { re = regex_map[pattern]; } else { try { - re = new Regex(pattern, RegexCompileFlags.DEFAULT, RegexMatchFlags.DEFAULT); + re = new Regex(pattern, 0, 0); regex_map[pattern] = re; } catch (Error err) { context.result_error("Invalid pattern: %s".printf(err.message), Sqlite.ERROR); diff --git a/src/db/Db.vala b/src/db/Db.vala index 7f76f2d..e537ee0 100644 --- a/src/db/Db.vala +++ b/src/db/Db.vala @@ -56,8 +56,12 @@ public VerifyResult verify_database(out string app_version, out int schema_versi return result; } - PhotoTable.clean_comments(); - VideoTable.clean_comments(); + try { + PhotoTable.clean_comments(); + VideoTable.clean_comments(); + } catch (DatabaseError err) { + debug("Ignoring database error while clean ing comments: %s", err.message); + } return VerifyResult.OK; diff --git a/src/db/VideoTable.vala b/src/db/VideoTable.vala index 67c50ba..753e02a 100644 --- a/src/db/VideoTable.vala +++ b/src/db/VideoTable.vala @@ -158,6 +158,8 @@ public class VideoTable : DatabaseTable { if (res != Sqlite.DONE) { if (res != Sqlite.CONSTRAINT) throw_error("VideoTable.add", res); + + return VideoID(); } // fill in ignored fields with database values diff --git a/src/dialogs/ExportDialog.vala b/src/dialogs/ExportDialog.vala index 5a61dc4..1f0a581 100644 --- a/src/dialogs/ExportDialog.vala +++ b/src/dialogs/ExportDialog.vala @@ -71,7 +71,7 @@ public class ExportDialog : Gtk.Dialog { format_combo = new Gtk.ComboBoxText(); format_add_option(UNMODIFIED_FORMAT_LABEL); format_add_option(CURRENT_FORMAT_LABEL); - foreach (PhotoFileFormat format in PhotoFileFormat.get_writeable()) { + foreach (PhotoFileFormat format in PhotoFileFormat.get_image_writeable()) { format_add_option(format.get_properties().get_user_visible_name()); } @@ -144,7 +144,7 @@ public class ExportDialog : Gtk.Dialog { selection_ticker++; } - error("format_set_active_text: text '%s' isn't in combo box", text); + critical("format_set_active_text: text '%s' isn't in combo box", text); } private PhotoFileFormat get_specified_format() { @@ -153,7 +153,7 @@ public class ExportDialog : Gtk.Dialog { index = NUM_SPECIAL_FORMATS; index -= NUM_SPECIAL_FORMATS; - PhotoFileFormat[] writeable_formats = PhotoFileFormat.get_writeable(); + PhotoFileFormat[] writeable_formats = PhotoFileFormat.get_image_writeable(); return writeable_formats[index]; } @@ -276,7 +276,7 @@ public class ExportDialog : Gtk.Dialog { if (format_combo.get_active_text() == UNMODIFIED_FORMAT_LABEL) { // if the user wishes to export the media unmodified, then we just copy the original - // files, so parameterizing size, quality, etc. is impossible -- these are all + // files, so parameterize size, quality, etc. is impossible -- these are all // just as they are in the original file. In this case, we set the scale constraint to // original and lock out all the controls constraint_combo.set_active(0); /* 0 == original size */ @@ -303,7 +303,9 @@ public class ExportDialog : Gtk.Dialog { constraint_combo.set_sensitive(true); bool jpeg = get_specified_format() == PhotoFileFormat.JFIF; quality_combo.sensitive = !original && jpeg; - export_metadata.sensitive = true; + + export_metadata.sensitive = get_specified_format().can_write_metadata(); + export_metadata.active = get_specified_format().can_write_metadata(); } } diff --git a/src/main.vala b/src/main.vala index e619178..1ea1900 100644 --- a/src/main.vala +++ b/src/main.vala @@ -262,9 +262,9 @@ void library_exec(string[] mounts) { message(" PNG : %s, gdk-pixbuf", png ? "yes" : "no"); message(" GIF : %s, gdk-pixbuf", gif ? "yes" : "no"); message(" TIFF : %s, gdk-pixbuf", tiff ? "yes" : "no"); - message(" JPEG XL: %s, gdk-pixbuf, %s meta-data", jxl ? "yes" : "no", can_read_bmff ? "yes" : "no"); - message(" AVIF : %s, gdk-pixbuf, %s meta-data", avif ? "yes" : "no", can_read_bmff ? "yes" : "no"); - message(" HEIF : %s, gdk-pixbuf, %s meta-data", heif ? "yes" : "no", can_read_bmff ? "yes" : "no"); + message(" JPEG XL: %s, gdk-pixbuf, %s meta-data", jxl ? "yes" : "no", can_read_bmff ? "read" : "no"); + message(" AVIF : %s, gdk-pixbuf, %s meta-data", avif ? "yes" : "no", can_read_bmff ? "read" : "no"); + message(" HEIF : %s, gdk-pixbuf, %s meta-data", heif ? "yes" : "no", can_read_bmff ? "read" : "no"); debug("%lf seconds to Gtk.main()", startup_timer.elapsed()); @@ -346,7 +346,16 @@ void dump_metadata (string filename) { void editing_exec(string filename, bool fullscreen) { File initial_file = File.new_for_commandline_arg(filename); - + + if (!initial_file.get_uri().has_prefix("file://")) { + if (!initial_file.get_uri().has_prefix("trash://")) { + initial_file = File.new_for_path(initial_file.get_path()); + } else { + var info = initial_file.query_info("standard::target-uri", FileQueryInfoFlags.NONE); + initial_file = File.new_for_uri(info.get_attribute_as_string("standard::target-uri")); + } + } + // preconfigure units Direct.preconfigure(initial_file); Db.preconfigure(null); @@ -542,7 +551,7 @@ void main(string[] args) { foreach (var arg in args[1:args.length]) { if (LibraryWindow.is_mount_uri_supported(arg)) { mounts += arg; - } else if (is_string_empty(filename) && !arg.contains("://")) { + } else if (is_string_empty(filename)) { filename = arg; } } diff --git a/src/meson.build b/src/meson.build index 25f967a..e6339f0 100644 --- a/src/meson.build +++ b/src/meson.build @@ -41,7 +41,7 @@ face_sources = (['faces/FacesBranch.vala', shotwell_deps = [gio, gee, sqlite, gtk, sqlite, posix, gphoto2, gstreamer_pbu, gudev, gexiv2, gmodule, unity, libraw, libexif, sw_plugin, webpdemux, webp, version, - portal] + portal, math] subdir('metadata') subdir('publishing') diff --git a/src/photos/AvifSupport.vala b/src/photos/AvifSupport.vala index 842f0fc..0df57a6 100644 --- a/src/photos/AvifSupport.vala +++ b/src/photos/AvifSupport.vala @@ -79,7 +79,7 @@ public class AvifWriter : PhotoFileWriter { } public override void write(Gdk.Pixbuf pixbuf, Jpeg.Quality quality) throws Error { - pixbuf.save(get_filepath(), "avif", "quality", "90", null); + pixbuf.save(get_filepath(), "avif", "quality", quality.get_pct_text(), null); } } @@ -89,7 +89,8 @@ public class AvifMetadataWriter : PhotoFileMetadataWriter { } public override void write_metadata(PhotoMetadata metadata) throws Error { - metadata.write_to_file(get_file()); + // TODO: Not yet implemented in gexiv2 + // metadata.write_to_file(get_file()); } } @@ -99,6 +100,19 @@ public class AvifFileFormatDriver : PhotoFileFormatDriver { public static void init() { instance = new AvifFileFormatDriver(); AvifFileFormatProperties.init(); + + var formats = Gdk.Pixbuf.get_formats(); + var seen = false; + can_write = true; + + foreach (var format in formats) { + if (format.get_name() == "avif") { + seen = true; + can_write = can_write && format.is_writable(); + } + } + + can_write = can_write && seen; } public static AvifFileFormatDriver get_instance() { @@ -112,13 +126,14 @@ public class AvifFileFormatDriver : PhotoFileFormatDriver { public override PhotoFileReader create_reader(string filepath) { return new AvifReader(filepath); } - + + static bool can_write; public override bool can_write_image() { - return true; + return AvifFileFormatDriver.can_write; } public override bool can_write_metadata() { - return true; + return false; } public override PhotoFileWriter? create_writer(string filepath) { diff --git a/src/photos/HeifSupport.vala b/src/photos/HeifSupport.vala index 58b9d9d..873e5a1 100644 --- a/src/photos/HeifSupport.vala +++ b/src/photos/HeifSupport.vala @@ -128,7 +128,7 @@ public class HeifFileFormatDriver : PhotoFileFormatDriver { } public override bool can_write_metadata() { - return true; + return false; } public override PhotoFileWriter? create_writer(string filepath) { diff --git a/src/photos/JfifSupport.vala b/src/photos/JfifSupport.vala index fc43663..ceca827 100644 --- a/src/photos/JfifSupport.vala +++ b/src/photos/JfifSupport.vala @@ -190,7 +190,11 @@ public class JfifWriter : PhotoFileWriter { } public override void write(Gdk.Pixbuf pixbuf, Jpeg.Quality quality) throws Error { - pixbuf.save(get_filepath(), "jpeg", "quality", quality.get_pct_text()); + if (pixbuf.has_alpha) { + apply_alpha_channel(pixbuf).save(get_filepath(), "jpeg", "quality", quality.get_pct_text()); + } else { + pixbuf.save(get_filepath(), "jpeg", "quality", quality.get_pct_text()); + } } } diff --git a/src/photos/PhotoFileFormat.vala b/src/photos/PhotoFileFormat.vala index 4c69de3..f7abc33 100644 --- a/src/photos/PhotoFileFormat.vala +++ b/src/photos/PhotoFileFormat.vala @@ -251,6 +251,7 @@ public enum PhotoFileFormat { return PhotoFileFormat.AVIF; case "heif": + case "heic": return PhotoFileFormat.HEIF; case "jxl": diff --git a/src/photos/PhotoMetadata.vala b/src/photos/PhotoMetadata.vala index 0624b41..3bf7b37 100644 --- a/src/photos/PhotoMetadata.vala +++ b/src/photos/PhotoMetadata.vala @@ -1044,6 +1044,10 @@ public class PhotoMetadata : MediaMetadata { public override string? get_comment() { var comment = get_first_string_interpreted (COMMENT_TAGS); + if (comment == null) { + return comment; + } + try { var re = new Regex("^charset=\\w+\\s*"); return re.replace(comment, -1, 0, "", RegexMatchFlags.DEFAULT); diff --git a/src/plugins/Plugins.vala b/src/plugins/Plugins.vala index cfab7e8..7078680 100644 --- a/src/plugins/Plugins.vala +++ b/src/plugins/Plugins.vala @@ -300,13 +300,6 @@ public int compare_extension_point_names(ExtensionPoint a, ExtensionPoint b) { return a.name.collate(b.name); } -private bool is_shared_library(File file) { - string name, ext; - disassemble_filename(file.get_basename(), out name, out ext); - - return ext == Module.SUFFIX; -} - private void search_for_plugins(File dir) throws Error { debug("Searching %s for plugins…", dir.get_path()); @@ -334,8 +327,9 @@ private void search_for_plugins(File dir) throws Error { break; case FileType.REGULAR: - if (is_shared_library(file)) + if (info.get_content_type() == "application/x-sharedlib") { load_module(file); + } break; default: diff --git a/src/plugins/SpitInterfaces.vala b/src/plugins/SpitInterfaces.vala index 94e6f95..0eabdd1 100644 --- a/src/plugins/SpitInterfaces.vala +++ b/src/plugins/SpitInterfaces.vala @@ -172,7 +172,7 @@ public class PluggableInfo : Object { public string? copyright {get; set; } public string? license_blurp { get; set; default = _("LGPL v2.1 or later"); } public string? license_url { get; set; default = "https://www.gnu.org/licenses/old-licenses/lgpl-2.1.html"; } - public string? website_url {get; set; default = "https://wiki.gnome.org/Apps/Shotwell";} + public string? website_url {get; set; default = "https://shotwell-project.org";} public string? website_name { get; set; default = _("Visit the Shotwell home page");} public string? translators {get; set; default = _("translator-credits"); } diff --git a/src/util/image.vala b/src/util/image.vala index 5b78a50..e46233d 100644 --- a/src/util/image.vala +++ b/src/util/image.vala @@ -185,8 +185,39 @@ public Gdk.Point subtract_points(Gdk.Point p1, Gdk.Point p2) { return result; } +Gdk.Pixbuf apply_alpha_channel(Gdk.Pixbuf source, bool strip = false) { + var dest = new Gdk.Pixbuf (source.colorspace, false, source.bits_per_sample, source.width, source.height); + uchar *sp = source.pixels; + uchar *dp = dest.pixels; + + for (int j = 0; j < source.height; j++) { + uchar *s = sp; + uchar *d = dp; + uchar *end = s + 4 * source.width; + while (s < end) { + if (strip) { + d[0] = s[0]; + d[1] = s[1]; + d[2] = s[2]; + } else { + double alpha = s[3] / 255.0; + d[0] = (uchar)Math.round((255.0 * (1.0 - alpha)) + (s[0] * alpha)); + d[1] = (uchar)Math.round((255.0 * (1.0 - alpha)) + (s[1] * alpha)); + d[2] = (uchar)Math.round((255.0 * (1.0 - alpha)) + (s[2] * alpha)); + } + s += 4; + d += 3; + } + + sp += source.rowstride; + dp += dest.rowstride; + } + + return dest; +} + // Converts XRGB/ARGB (Cairo)-formatted pixels to RGBA (GDK). -void fix_cairo_pixbuf(Gdk.Pixbuf pixbuf) { +void argb2rgba(Gdk.Pixbuf pixbuf) { uchar *gdk_pixels = pixbuf.pixels; for (int j = 0 ; j < pixbuf.height; ++j) { uchar *p = gdk_pixels; @@ -274,9 +305,8 @@ Gdk.Pixbuf rotate_arb(Gdk.Pixbuf source_pixbuf, double angle) { // prepare the newly-drawn image for use by // the rest of the pipeline. - fix_cairo_pixbuf(dest_pixbuf); - - return dest_pixbuf; + argb2rgba(dest_pixbuf); + return apply_alpha_channel(dest_pixbuf, true); } /** diff --git a/src/util/string.vala b/src/util/string.vala index 5ca4680..521e2ba 100644 --- a/src/util/string.vala +++ b/src/util/string.vala @@ -203,7 +203,7 @@ public string remove_diacritics(string istring) { case UnicodeType.FORMAT: case UnicodeType.UNASSIGNED: case UnicodeType.NON_SPACING_MARK: - case UnicodeType.COMBINING_MARK: + case UnicodeType.SPACING_MARK: case UnicodeType.ENCLOSING_MARK: // Ignore those continue; -- cgit v1.2.3