Compare commits
	
		
			No commits in common. "cc59e537dab61d6dc1799498b891f6e4ee1b96bf" and "f56c40cf0021cd60b4ee8405d06a3a506f588d1f" have entirely different histories.
		
	
	
		
			cc59e537da
			...
			f56c40cf00
		
	
		
							
								
								
									
										4
									
								
								.gitmodules
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										4
									
								
								.gitmodules
									
									
									
									
										vendored
									
									
								
							| @ -1,6 +1,6 @@ | |||||||
| [submodule "wuffs-mirror-release-c"] | [submodule "wuffs-mirror-release-c"] | ||||||
| 	path = submodules/wuffs-mirror-release-c | 	path = wuffs-mirror-release-c | ||||||
| 	url = https://github.com/google/wuffs-mirror-release-c | 	url = https://github.com/google/wuffs-mirror-release-c | ||||||
| [submodule "liberty"] | [submodule "liberty"] | ||||||
| 	path = submodules/liberty | 	path = liberty | ||||||
| 	url = https://git.janouch.name/p/liberty.git | 	url = https://git.janouch.name/p/liberty.git | ||||||
|  | |||||||
							
								
								
									
										13
									
								
								README.adoc
									
									
									
									
									
								
							
							
						
						
									
										13
									
								
								README.adoc
									
									
									
									
									
								
							| @ -2,7 +2,7 @@ fiv | |||||||
| === | === | ||||||
| 
 | 
 | ||||||
| 'fiv' is a slightly unconventional, general-purpose image browser and viewer | 'fiv' is a slightly unconventional, general-purpose image browser and viewer | ||||||
| for Linux (as well as macOS and Windows, though these have known issues). | for Linux (that said, macOS and Windows ports are possible). | ||||||
| 
 | 
 | ||||||
| image::docs/fiv.webp["Screenshot of both the browser and the viewer"] | image::docs/fiv.webp["Screenshot of both the browser and the viewer"] | ||||||
| 
 | 
 | ||||||
| @ -40,16 +40,15 @@ Building and Running | |||||||
| -------------------- | -------------------- | ||||||
| Build-only dependencies: | Build-only dependencies: | ||||||
|  Meson, pkg-config, asciidoctor or asciidoc (recommended but optional) + |  Meson, pkg-config, asciidoctor or asciidoc (recommended but optional) + | ||||||
| Runtime dependencies: gtk+-3.0, glib>=2.64, pixman-1, shared-mime-info, | Runtime dependencies: | ||||||
|  libturbojpeg, libwebp, librsvg-2.0 (for icons) + |  gtk+-3.0, glib>=2.64, pixman-1, shared-mime-info, libturbojpeg, libwebp + | ||||||
| Optional dependencies: lcms2, Little CMS fast float plugin, | Optional dependencies: lcms2, LibRaw, librsvg-2.0, xcursor, libheif, libtiff, | ||||||
|  LibRaw, librsvg-2.0, xcursor, libheif, libtiff, ExifTool, |  ExifTool, resvg (unstable API, needs to be requested explicitly) + | ||||||
|  resvg (unstable API, needs to be requested explicitly) + |  | ||||||
| Runtime dependencies for reverse image search: | Runtime dependencies for reverse image search: | ||||||
|  xdg-utils, cURL, jq |  xdg-utils, cURL, jq | ||||||
| 
 | 
 | ||||||
|  $ git clone --recursive https://git.janouch.name/p/fiv.git |  $ git clone --recursive https://git.janouch.name/p/fiv.git | ||||||
|  $ meson setup builddir |  $ meson builddir | ||||||
|  $ cd builddir |  $ cd builddir | ||||||
|  $ meson compile |  $ meson compile | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -640,15 +640,11 @@ materialize_icon(FivBrowser *self, Entry *entry) | |||||||
| 	// of using GLib to look up icons for us, derive a list from a guessed
 | 	// of using GLib to look up icons for us, derive a list from a guessed
 | ||||||
| 	// MIME type, with "-symbolic" prefixes and fallbacks,
 | 	// MIME type, with "-symbolic" prefixes and fallbacks,
 | ||||||
| 	// and use gtk_icon_theme_choose_icon() instead.
 | 	// and use gtk_icon_theme_choose_icon() instead.
 | ||||||
|  | 	// TODO(p): Make sure we have /some/ icon for every entry.
 | ||||||
| 	// TODO(p): We might want to populate these on an as-needed basis.
 | 	// TODO(p): We might want to populate these on an as-needed basis.
 | ||||||
| 	GtkIconTheme *theme = gtk_icon_theme_get_default(); | 	GtkIconInfo *icon_info = gtk_icon_theme_lookup_by_gicon( | ||||||
| 	GtkIconInfo *icon_info = gtk_icon_theme_lookup_by_gicon(theme, entry->icon, | 		gtk_icon_theme_get_default(), entry->icon, self->item_height / 2, | ||||||
| 		self->item_height / 2, GTK_ICON_LOOKUP_FORCE_SYMBOLIC); | 		GTK_ICON_LOOKUP_FORCE_SYMBOLIC); | ||||||
| 	if (!icon_info) { |  | ||||||
| 		// This icon is included within GTK+.
 |  | ||||||
| 		icon_info = gtk_icon_theme_lookup_icon(theme, "text-x-generic", |  | ||||||
| 			self->item_height / 2, GTK_ICON_LOOKUP_FORCE_SYMBOLIC); |  | ||||||
| 	} |  | ||||||
| 	if (!icon_info) | 	if (!icon_info) | ||||||
| 		return; | 		return; | ||||||
| 
 | 
 | ||||||
|  | |||||||
							
								
								
									
										2
									
								
								fiv-io.c
									
									
									
									
									
								
							
							
						
						
									
										2
									
								
								fiv-io.c
									
									
									
									
									
								
							| @ -84,7 +84,7 @@ | |||||||
| #define WUFFS_CONFIG__MODULE__PNG | #define WUFFS_CONFIG__MODULE__PNG | ||||||
| #define WUFFS_CONFIG__MODULE__TGA | #define WUFFS_CONFIG__MODULE__TGA | ||||||
| #define WUFFS_CONFIG__MODULE__ZLIB | #define WUFFS_CONFIG__MODULE__ZLIB | ||||||
| #include "submodules/wuffs-mirror-release-c/release/c/wuffs-v0.3.c" | #include "wuffs-mirror-release-c/release/c/wuffs-v0.3.c" | ||||||
| 
 | 
 | ||||||
| #include "fiv-io.h" | #include "fiv-io.h" | ||||||
| 
 | 
 | ||||||
|  | |||||||
							
								
								
									
										385
									
								
								fiv.c
									
									
									
									
									
								
							
							
						
						
									
										385
									
								
								fiv.c
									
									
									
									
									
								
							| @ -43,11 +43,6 @@ | |||||||
| #include "fiv-thumbnail.h" | #include "fiv-thumbnail.h" | ||||||
| #include "fiv-view.h" | #include "fiv-view.h" | ||||||
| 
 | 
 | ||||||
| #ifdef HAVE_LCMS2_FAST_FLOAT |  | ||||||
| #include <lcms2.h> |  | ||||||
| #include <lcms2_fast_float.h> |  | ||||||
| #endif  // HAVE_LCMS2_FAST_FLOAT
 |  | ||||||
| 
 |  | ||||||
| // --- Utilities ---------------------------------------------------------------
 | // --- Utilities ---------------------------------------------------------------
 | ||||||
| 
 | 
 | ||||||
| static void exit_fatal(const char *format, ...) G_GNUC_PRINTF(1, 2); | static void exit_fatal(const char *format, ...) G_GNUC_PRINTF(1, 2); | ||||||
| @ -393,6 +388,12 @@ on_about_draw(GtkWidget *widget, cairo_t *cr, gpointer user_data) | |||||||
| 	GtkStyleContext *style = gtk_widget_get_style_context(widget); | 	GtkStyleContext *style = gtk_widget_get_style_context(widget); | ||||||
| 	gtk_render_background(style, cr, 0, 0, allocation.width, allocation.height); | 	gtk_render_background(style, cr, 0, 0, allocation.width, allocation.height); | ||||||
| 
 | 
 | ||||||
|  | 	// The transformation matrix turns out/is applied wrongly on Quartz.
 | ||||||
|  | 	gboolean broken_backend = cairo_surface_get_type(cairo_get_target(cr)) == | ||||||
|  | 		CAIRO_SURFACE_TYPE_QUARTZ; | ||||||
|  | 	if (broken_backend) | ||||||
|  | 		cairo_push_group(cr); | ||||||
|  | 
 | ||||||
| 	cairo_translate(cr, (allocation.width - ABOUT_SIZE * ABOUT_SCALE) / 2, | 	cairo_translate(cr, (allocation.width - ABOUT_SIZE * ABOUT_SCALE) / 2, | ||||||
| 		ABOUT_SIZE * ABOUT_SCALE / 4); | 		ABOUT_SIZE * ABOUT_SCALE / 4); | ||||||
| 	cairo_scale(cr, ABOUT_SCALE, ABOUT_SCALE); | 	cairo_scale(cr, ABOUT_SCALE, ABOUT_SCALE); | ||||||
| @ -418,6 +419,11 @@ on_about_draw(GtkWidget *widget, cairo_t *cr, gpointer user_data) | |||||||
| 
 | 
 | ||||||
| 	cairo_restore(cr); | 	cairo_restore(cr); | ||||||
| 	draw_ligature(cr); | 	draw_ligature(cr); | ||||||
|  | 
 | ||||||
|  | 	if (broken_backend) { | ||||||
|  | 		cairo_pop_group_to_source(cr); | ||||||
|  | 		cairo_paint(cr); | ||||||
|  | 	} | ||||||
| 	return TRUE; | 	return TRUE; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| @ -2092,12 +2098,163 @@ static const char stylesheet[] = "@define-color fiv-tile @content_view_bg; \ | |||||||
| 	} \ | 	} \ | ||||||
| 	.fiv-information label { padding: 0 4px; }"; | 	.fiv-information label { padding: 0 4px; }"; | ||||||
| 
 | 
 | ||||||
| static void | static FivThumbnailSize | ||||||
| on_app_startup(GApplication *app, G_GNUC_UNUSED gpointer user_data) | output_thumbnail_prologue(gchar **uris, const char *size_arg) | ||||||
| { | { | ||||||
| 	// We can't prevent GApplication from adding --gapplication-service.
 | 	if (!uris) | ||||||
| 	if (g_application_get_flags(app) & G_APPLICATION_IS_SERVICE) | 		exit_fatal("No path given"); | ||||||
| 		exit(EXIT_FAILURE); | 	if (uris[1]) | ||||||
|  | 		exit_fatal("Only one thumbnail at a time may be produced"); | ||||||
|  | 
 | ||||||
|  | 	FivThumbnailSize size = FIV_THUMBNAIL_SIZE_COUNT; | ||||||
|  | 	if (size_arg) { | ||||||
|  | 		for (size = 0; size < FIV_THUMBNAIL_SIZE_COUNT; size++) { | ||||||
|  | 			if (!strcmp( | ||||||
|  | 					fiv_thumbnail_sizes[size].thumbnail_spec_name, size_arg)) | ||||||
|  | 				break; | ||||||
|  | 		} | ||||||
|  | 		if (size >= FIV_THUMBNAIL_SIZE_COUNT) | ||||||
|  | 			exit_fatal("unknown thumbnail size: %s", size_arg); | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | #ifdef G_OS_WIN32 | ||||||
|  | 	_setmode(fileno(stdout), _O_BINARY); | ||||||
|  | #endif | ||||||
|  | 	return size; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static void | ||||||
|  | output_thumbnail_for_search(gchar **uris, const char *size_arg) | ||||||
|  | { | ||||||
|  | 	FivThumbnailSize size = output_thumbnail_prologue(uris, size_arg); | ||||||
|  | 
 | ||||||
|  | 	GError *error = NULL; | ||||||
|  | 	GFile *file = g_file_new_for_uri(uris[0]); | ||||||
|  | 	cairo_surface_t *surface = NULL; | ||||||
|  | 	GBytes *bytes = NULL; | ||||||
|  | 	if ((surface = fiv_thumbnail_produce(file, size, &error)) && | ||||||
|  | 		(bytes = fiv_io_serialize_for_search(surface, &error))) { | ||||||
|  | 		fwrite( | ||||||
|  | 			g_bytes_get_data(bytes, NULL), 1, g_bytes_get_size(bytes), stdout); | ||||||
|  | 		g_bytes_unref(bytes); | ||||||
|  | 	} else { | ||||||
|  | 		g_assert(error != NULL); | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	g_object_unref(file); | ||||||
|  | 	if (error) | ||||||
|  | 		exit_fatal("%s", error->message); | ||||||
|  | 
 | ||||||
|  | 	cairo_surface_destroy(surface); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static void | ||||||
|  | output_thumbnail(gchar **uris, gboolean extract, const char *size_arg) | ||||||
|  | { | ||||||
|  | 	FivThumbnailSize size = output_thumbnail_prologue(uris, size_arg); | ||||||
|  | 
 | ||||||
|  | 	GError *error = NULL; | ||||||
|  | 	GFile *file = g_file_new_for_uri(uris[0]); | ||||||
|  | 	cairo_surface_t *surface = NULL; | ||||||
|  | 	if (extract && (surface = fiv_thumbnail_extract(file, size, &error))) | ||||||
|  | 		fiv_io_serialize_to_stdout(surface, FIV_IO_SERIALIZE_LOW_QUALITY); | ||||||
|  | 	else if (size_arg && | ||||||
|  | 		(g_clear_error(&error), | ||||||
|  | 			(surface = fiv_thumbnail_produce(file, size, &error)))) | ||||||
|  | 		fiv_io_serialize_to_stdout(surface, 0); | ||||||
|  | 	else | ||||||
|  | 		g_assert(error != NULL); | ||||||
|  | 
 | ||||||
|  | 	g_object_unref(file); | ||||||
|  | 	if (error) | ||||||
|  | 		exit_fatal("%s", error->message); | ||||||
|  | 
 | ||||||
|  | 	cairo_surface_destroy(surface); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | int | ||||||
|  | main(int argc, char *argv[]) | ||||||
|  | { | ||||||
|  | 	gboolean show_version = FALSE, show_supported_media_types = FALSE, | ||||||
|  | 		invalidate_cache = FALSE, browse = FALSE, extract_thumbnail = FALSE; | ||||||
|  | 	gchar **args = NULL, *thumbnail_size = NULL, *thumbnail_size_search = NULL; | ||||||
|  | 	const GOptionEntry options[] = { | ||||||
|  | 		{G_OPTION_REMAINING, 0, 0, G_OPTION_ARG_FILENAME_ARRAY, &args, | ||||||
|  | 			NULL, "[PATH | URI]..."}, | ||||||
|  | 		{"browse", 0, G_OPTION_FLAG_IN_MAIN, | ||||||
|  | 			G_OPTION_ARG_NONE, &browse, | ||||||
|  | 			"Start in filesystem browsing mode", NULL}, | ||||||
|  | 		{"invalidate-cache", 0, G_OPTION_FLAG_IN_MAIN, | ||||||
|  | 			G_OPTION_ARG_NONE, &invalidate_cache, | ||||||
|  | 			"Invalidate the wide thumbnail cache", NULL}, | ||||||
|  | 		{"list-supported-media-types", 0, G_OPTION_FLAG_IN_MAIN, | ||||||
|  | 			G_OPTION_ARG_NONE, &show_supported_media_types, | ||||||
|  | 			"Output supported media types and exit", NULL}, | ||||||
|  | 		{"version", 'V', G_OPTION_FLAG_IN_MAIN, G_OPTION_ARG_NONE, | ||||||
|  | 			&show_version, "Output version information and exit", NULL}, | ||||||
|  | 		{}, | ||||||
|  | 	}; | ||||||
|  | 	const GOptionEntry options_internal[] = { | ||||||
|  | 		{"extract-thumbnail", 0, 0, | ||||||
|  | 			G_OPTION_ARG_NONE, &extract_thumbnail, | ||||||
|  | 			"Output any embedded thumbnail (superseding --thumbnail)", NULL}, | ||||||
|  | 		{"thumbnail", 0, 0, | ||||||
|  | 			G_OPTION_ARG_STRING, &thumbnail_size, | ||||||
|  | 			"Generate thumbnails, up to SIZE, and output that size", "SIZE"}, | ||||||
|  | 		{"thumbnail-for-search", 0, 0, | ||||||
|  | 			G_OPTION_ARG_STRING, &thumbnail_size_search, | ||||||
|  | 			"Output an image file suitable for searching by content", "SIZE"}, | ||||||
|  | 		{}, | ||||||
|  | 	}; | ||||||
|  | 
 | ||||||
|  | 	GOptionContext *context = | ||||||
|  | 		g_option_context_new(" - Image browser and viewer"); | ||||||
|  | 	g_option_context_add_group(context, gtk_get_option_group(TRUE)); | ||||||
|  | 	g_option_context_add_main_entries(context, options, NULL); | ||||||
|  | 
 | ||||||
|  | 	GOptionGroup *internals = g_option_group_new( | ||||||
|  | 		"internal", "Internal Options:", "Show internal options", NULL, NULL); | ||||||
|  | 	g_option_group_add_entries(internals, options_internal); | ||||||
|  | 	g_option_context_add_group(context, internals); | ||||||
|  | 
 | ||||||
|  | 	GError *error = NULL; | ||||||
|  | 	gboolean initialized = | ||||||
|  | 		g_option_context_parse(context, &argc, &argv, &error); | ||||||
|  | 	g_option_context_free(context); | ||||||
|  | 	if (show_version) { | ||||||
|  | 		const char *version = PROJECT_VERSION; | ||||||
|  | 		printf("%s %s\n", PROJECT_NAME, &version[*version == 'v']); | ||||||
|  | 		return 0; | ||||||
|  | 	} | ||||||
|  | 	if (show_supported_media_types) { | ||||||
|  | 		char **types = fiv_io_all_supported_media_types(); | ||||||
|  | 		for (char **p = types; *p; p++) | ||||||
|  | 			g_print("%s\n", *p); | ||||||
|  | 		g_strfreev(types); | ||||||
|  | 		return 0; | ||||||
|  | 	} | ||||||
|  | 	if (invalidate_cache) { | ||||||
|  | 		fiv_thumbnail_invalidate(); | ||||||
|  | 		return 0; | ||||||
|  | 	} | ||||||
|  | 	if (!initialized) | ||||||
|  | 		exit_fatal("%s", error->message); | ||||||
|  | 
 | ||||||
|  | 	// Normalize all arguments to URIs.
 | ||||||
|  | 	for (gsize i = 0; args && args[i]; i++) { | ||||||
|  | 		GFile *resolved = g_file_new_for_commandline_arg(args[i]); | ||||||
|  | 		g_free(args[i]); | ||||||
|  | 		args[i] = g_file_get_uri(resolved); | ||||||
|  | 		g_object_unref(resolved); | ||||||
|  | 	} | ||||||
|  | 	if (thumbnail_size_search) { | ||||||
|  | 		output_thumbnail_for_search(args, thumbnail_size_search); | ||||||
|  | 		return 0; | ||||||
|  | 	} | ||||||
|  | 	if (extract_thumbnail || thumbnail_size) { | ||||||
|  | 		output_thumbnail(args, extract_thumbnail, thumbnail_size); | ||||||
|  | 		return 0; | ||||||
|  | 	} | ||||||
| 
 | 
 | ||||||
| 	// It doesn't make much sense to have command line arguments able to
 | 	// It doesn't make much sense to have command line arguments able to
 | ||||||
| 	// resolve to the VFS they may end up being contained within.
 | 	// resolve to the VFS they may end up being contained within.
 | ||||||
| @ -2200,9 +2357,9 @@ on_app_startup(GApplication *app, G_GNUC_UNUSED gpointer user_data) | |||||||
| 	gtk_container_add(GTK_CONTAINER(g.stack), g.view_box); | 	gtk_container_add(GTK_CONTAINER(g.stack), g.view_box); | ||||||
| 	gtk_container_add(GTK_CONTAINER(g.stack), g.browser_paned); | 	gtk_container_add(GTK_CONTAINER(g.stack), g.browser_paned); | ||||||
| 
 | 
 | ||||||
| 	g.window = gtk_application_window_new(GTK_APPLICATION(app)); | 	g.window = gtk_window_new(GTK_WINDOW_TOPLEVEL); | ||||||
| 	g_signal_connect_swapped(g.window, "destroy", | 	g_signal_connect(g.window, "destroy", | ||||||
| 		G_CALLBACK(g_application_quit), app); | 		G_CALLBACK(gtk_main_quit), NULL); | ||||||
| 	g_signal_connect(g.window, "key-press-event", | 	g_signal_connect(g.window, "key-press-event", | ||||||
| 		G_CALLBACK(on_key_press), NULL); | 		G_CALLBACK(on_key_press), NULL); | ||||||
| 	g_signal_connect(g.window, "window-state-event", | 	g_signal_connect(g.window, "window-state-event", | ||||||
| @ -2253,34 +2410,24 @@ on_app_startup(GApplication *app, G_GNUC_UNUSED gpointer user_data) | |||||||
| 
 | 
 | ||||||
| 	// XXX: The widget wants to read the display's profile. The realize is ugly.
 | 	// XXX: The widget wants to read the display's profile. The realize is ugly.
 | ||||||
| 	gtk_widget_realize(g.view); | 	gtk_widget_realize(g.view); | ||||||
| } |  | ||||||
| 
 | 
 | ||||||
| static struct { |  | ||||||
| 	gboolean browse, extract_thumbnail; |  | ||||||
| 	gchar **args, *thumbnail_size, *thumbnail_size_search; |  | ||||||
| } o; |  | ||||||
| 
 |  | ||||||
| static void |  | ||||||
| on_app_activate( |  | ||||||
| 	G_GNUC_UNUSED GApplication *app, G_GNUC_UNUSED gpointer user_data) |  | ||||||
| { |  | ||||||
| 	// XXX: We follow the behaviour of Firefox and Eye of GNOME, which both
 | 	// XXX: We follow the behaviour of Firefox and Eye of GNOME, which both
 | ||||||
| 	// interpret multiple command line arguments differently, as a collection.
 | 	// interpret multiple command line arguments differently, as a collection.
 | ||||||
| 	// However, single-element collections are unrepresentable this way.
 | 	// However, single-element collections are unrepresentable this way.
 | ||||||
| 	// Should we allow multiple targets only in a special new mode?
 | 	// Should we allow multiple targets only in a special new mode?
 | ||||||
| 	g.files_index = -1; | 	g.files_index = -1; | ||||||
| 	if (o.args) { | 	if (args) { | ||||||
| 		const gchar *target = *o.args; | 		const gchar *target = *args; | ||||||
| 		if (o.args[1]) { | 		if (args[1]) { | ||||||
| 			fiv_collection_reload(o.args); | 			fiv_collection_reload(args); | ||||||
| 			target = FIV_COLLECTION_SCHEME ":/"; | 			target = FIV_COLLECTION_SCHEME ":/"; | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
| 		GFile *file = g_file_new_for_uri(target); | 		GFile *file = g_file_new_for_uri(target); | ||||||
| 		open_any_file(file, o.browse); | 		open_any_file(file, browse); | ||||||
| 		g_object_unref(file); | 		g_object_unref(file); | ||||||
|  | 		g_strfreev(args); | ||||||
| 	} | 	} | ||||||
| 
 |  | ||||||
| 	if (!g.directory) { | 	if (!g.directory) { | ||||||
| 		GFile *file = g_file_new_for_path("."); | 		GFile *file = g_file_new_for_path("."); | ||||||
| 		open_any_file(file, FALSE); | 		open_any_file(file, FALSE); | ||||||
| @ -2288,184 +2435,6 @@ on_app_activate( | |||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	gtk_widget_show(g.window); | 	gtk_widget_show(g.window); | ||||||
| } | 	gtk_main(); | ||||||
| 
 |  | ||||||
| // --- Plumbing ----------------------------------------------------------------
 |  | ||||||
| 
 |  | ||||||
| static FivThumbnailSize |  | ||||||
| output_thumbnail_prologue(gchar **uris, const char *size_arg) |  | ||||||
| { |  | ||||||
| 	if (!uris) |  | ||||||
| 		exit_fatal("No path given"); |  | ||||||
| 	if (uris[1]) |  | ||||||
| 		exit_fatal("Only one thumbnail at a time may be produced"); |  | ||||||
| 
 |  | ||||||
| 	FivThumbnailSize size = FIV_THUMBNAIL_SIZE_COUNT; |  | ||||||
| 	if (size_arg) { |  | ||||||
| 		for (size = 0; size < FIV_THUMBNAIL_SIZE_COUNT; size++) { |  | ||||||
| 			if (!strcmp( |  | ||||||
| 					fiv_thumbnail_sizes[size].thumbnail_spec_name, size_arg)) |  | ||||||
| 				break; |  | ||||||
| 		} |  | ||||||
| 		if (size >= FIV_THUMBNAIL_SIZE_COUNT) |  | ||||||
| 			exit_fatal("unknown thumbnail size: %s", size_arg); |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| #ifdef G_OS_WIN32 |  | ||||||
| 	_setmode(fileno(stdout), _O_BINARY); |  | ||||||
| #endif |  | ||||||
| 	return size; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| static void |  | ||||||
| output_thumbnail_for_search(gchar **uris, const char *size_arg) |  | ||||||
| { |  | ||||||
| 	FivThumbnailSize size = output_thumbnail_prologue(uris, size_arg); |  | ||||||
| 
 |  | ||||||
| 	GError *error = NULL; |  | ||||||
| 	GFile *file = g_file_new_for_uri(uris[0]); |  | ||||||
| 	cairo_surface_t *surface = NULL; |  | ||||||
| 	GBytes *bytes = NULL; |  | ||||||
| 	if ((surface = fiv_thumbnail_produce(file, size, &error)) && |  | ||||||
| 		(bytes = fiv_io_serialize_for_search(surface, &error))) { |  | ||||||
| 		fwrite( |  | ||||||
| 			g_bytes_get_data(bytes, NULL), 1, g_bytes_get_size(bytes), stdout); |  | ||||||
| 		g_bytes_unref(bytes); |  | ||||||
| 	} else { |  | ||||||
| 		g_assert(error != NULL); |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	g_object_unref(file); |  | ||||||
| 	if (error) |  | ||||||
| 		exit_fatal("%s", error->message); |  | ||||||
| 
 |  | ||||||
| 	cairo_surface_destroy(surface); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| static void |  | ||||||
| output_thumbnail(gchar **uris, gboolean extract, const char *size_arg) |  | ||||||
| { |  | ||||||
| 	FivThumbnailSize size = output_thumbnail_prologue(uris, size_arg); |  | ||||||
| 
 |  | ||||||
| 	GError *error = NULL; |  | ||||||
| 	GFile *file = g_file_new_for_uri(uris[0]); |  | ||||||
| 	cairo_surface_t *surface = NULL; |  | ||||||
| 	if (extract && (surface = fiv_thumbnail_extract(file, size, &error))) |  | ||||||
| 		fiv_io_serialize_to_stdout(surface, FIV_IO_SERIALIZE_LOW_QUALITY); |  | ||||||
| 	else if (size_arg && |  | ||||||
| 		(g_clear_error(&error), |  | ||||||
| 			(surface = fiv_thumbnail_produce(file, size, &error)))) |  | ||||||
| 		fiv_io_serialize_to_stdout(surface, 0); |  | ||||||
| 	else |  | ||||||
| 		g_assert(error != NULL); |  | ||||||
| 
 |  | ||||||
| 	g_object_unref(file); |  | ||||||
| 	if (error) |  | ||||||
| 		exit_fatal("%s", error->message); |  | ||||||
| 
 |  | ||||||
| 	cairo_surface_destroy(surface); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| static gint |  | ||||||
| on_app_handle_local_options(G_GNUC_UNUSED GApplication *app, |  | ||||||
| 	GVariantDict *options, G_GNUC_UNUSED gpointer user_data) |  | ||||||
| { |  | ||||||
| 	if (g_variant_dict_contains(options, "version")) { |  | ||||||
| 		const char *version = PROJECT_VERSION; |  | ||||||
| 		printf("%s %s\n", PROJECT_NAME, &version[*version == 'v']); |  | ||||||
| 	return 0; | 	return 0; | ||||||
| } | } | ||||||
| 	if (g_variant_dict_contains(options, "list-supported-media-types")) { |  | ||||||
| 		char **types = fiv_io_all_supported_media_types(); |  | ||||||
| 		for (char **p = types; *p; p++) |  | ||||||
| 			g_print("%s\n", *p); |  | ||||||
| 		g_strfreev(types); |  | ||||||
| 		return 0; |  | ||||||
| 	} |  | ||||||
| 	if (g_variant_dict_contains(options, "invalidate-cache")) { |  | ||||||
| 		fiv_thumbnail_invalidate(); |  | ||||||
| 		return 0; |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	// TODO(p): Use Little CMS with contexts instead.
 |  | ||||||
| #ifdef HAVE_LCMS2_FAST_FLOAT |  | ||||||
| 	cmsPlugin(cmsFastFloatExtensions()); |  | ||||||
| #endif  // HAVE_LCMS2_FAST_FLOAT
 |  | ||||||
| 
 |  | ||||||
| 	// Normalize all arguments to URIs, and run thumbnailing modes first.
 |  | ||||||
| 	for (gsize i = 0; o.args && o.args[i]; i++) { |  | ||||||
| 		GFile *resolved = g_file_new_for_commandline_arg(o.args[i]); |  | ||||||
| 		g_free(o.args[i]); |  | ||||||
| 		o.args[i] = g_file_get_uri(resolved); |  | ||||||
| 		g_object_unref(resolved); |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	// These come from an option group that doesn't get copied to "options".
 |  | ||||||
| 	if (o.thumbnail_size_search) { |  | ||||||
| 		output_thumbnail_for_search(o.args, o.thumbnail_size_search); |  | ||||||
| 		return 0; |  | ||||||
| 	} |  | ||||||
| 	if (o.extract_thumbnail || o.thumbnail_size) { |  | ||||||
| 		output_thumbnail(o.args, o.extract_thumbnail, o.thumbnail_size); |  | ||||||
| 		return 0; |  | ||||||
| 	} |  | ||||||
| 	return -1; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| int |  | ||||||
| main(int argc, char *argv[]) |  | ||||||
| { |  | ||||||
| 	const GOptionEntry options[] = { |  | ||||||
| 		{G_OPTION_REMAINING, 0, 0, |  | ||||||
| 			G_OPTION_ARG_FILENAME_ARRAY, &o.args, |  | ||||||
| 			NULL, "[PATH | URI]..."}, |  | ||||||
| 		{"browse", 0, G_OPTION_FLAG_IN_MAIN, |  | ||||||
| 			G_OPTION_ARG_NONE, &o.browse, |  | ||||||
| 			"Start in filesystem browsing mode", NULL}, |  | ||||||
| 		{"invalidate-cache", 0, G_OPTION_FLAG_IN_MAIN, |  | ||||||
| 			G_OPTION_ARG_NONE, NULL, |  | ||||||
| 			"Invalidate the wide thumbnail cache", NULL}, |  | ||||||
| 		{"list-supported-media-types", 0, G_OPTION_FLAG_IN_MAIN, |  | ||||||
| 			G_OPTION_ARG_NONE, NULL, |  | ||||||
| 			"Output supported media types and exit", NULL}, |  | ||||||
| 		{"version", 'V', G_OPTION_FLAG_IN_MAIN, |  | ||||||
| 			G_OPTION_ARG_NONE, NULL, |  | ||||||
| 			"Output version information and exit", NULL}, |  | ||||||
| 		{}, |  | ||||||
| 	}; |  | ||||||
| 	const GOptionEntry options_internal[] = { |  | ||||||
| 		{"extract-thumbnail", 0, 0, |  | ||||||
| 			G_OPTION_ARG_NONE, &o.extract_thumbnail, |  | ||||||
| 			"Output any embedded thumbnail (superseding --thumbnail)", NULL}, |  | ||||||
| 		{"thumbnail", 0, 0, |  | ||||||
| 			G_OPTION_ARG_STRING, &o.thumbnail_size, |  | ||||||
| 			"Generate thumbnails, up to SIZE, and output that size", "SIZE"}, |  | ||||||
| 		{"thumbnail-for-search", 0, 0, |  | ||||||
| 			G_OPTION_ARG_STRING, &o.thumbnail_size_search, |  | ||||||
| 			"Output an image file suitable for searching by content", "SIZE"}, |  | ||||||
| 		{}, |  | ||||||
| 	}; |  | ||||||
| 
 |  | ||||||
| 	// We never get the ::open signal, thanks to G_OPTION_ARG_FILENAME_ARRAY.
 |  | ||||||
| 	GtkApplication *app = gtk_application_new(NULL, G_APPLICATION_NON_UNIQUE); |  | ||||||
| 	g_application_set_option_context_parameter_string( |  | ||||||
| 		G_APPLICATION(app), " - Image browser and viewer"); |  | ||||||
| 	g_application_add_main_option_entries(G_APPLICATION(app), options); |  | ||||||
| 
 |  | ||||||
| 	GOptionGroup *internals = g_option_group_new( |  | ||||||
| 		"internal", "Internal Options:", "Show internal options", NULL, NULL); |  | ||||||
| 	g_option_group_add_entries(internals, options_internal); |  | ||||||
| 	g_application_add_option_group(G_APPLICATION(app), internals); |  | ||||||
| 
 |  | ||||||
| 	g_signal_connect(app, "handle-local-options", |  | ||||||
| 		G_CALLBACK(on_app_handle_local_options), NULL); |  | ||||||
| 	g_signal_connect(app, "startup", |  | ||||||
| 		G_CALLBACK(on_app_startup), NULL); |  | ||||||
| 	g_signal_connect(app, "activate", |  | ||||||
| 		G_CALLBACK(on_app_activate), NULL); |  | ||||||
| 
 |  | ||||||
| 	int status = g_application_run(G_APPLICATION(app), argc, argv); |  | ||||||
| 	g_object_unref(app); |  | ||||||
| 	g_strfreev(o.args); |  | ||||||
| 	return status; |  | ||||||
| } |  | ||||||
|  | |||||||
							
								
								
									
										25
									
								
								meson.build
									
									
									
									
									
								
							
							
						
						
									
										25
									
								
								meson.build
									
									
									
									
									
								
							| @ -25,12 +25,11 @@ libjpegqs = dependency('libjpegqs', required : get_option('libjpegqs'), | |||||||
| lcms2 = dependency('lcms2', required : get_option('lcms2')) | lcms2 = dependency('lcms2', required : get_option('lcms2')) | ||||||
| # Note that only libraw_r is thread-safe, but we'll just run it out-of process. | # Note that only libraw_r is thread-safe, but we'll just run it out-of process. | ||||||
| libraw = dependency('libraw', required : get_option('libraw')) | libraw = dependency('libraw', required : get_option('libraw')) | ||||||
| # This is a direct runtime dependency, but its usage may be disabled for images. |  | ||||||
| librsvg = dependency('librsvg-2.0', required : get_option('librsvg')) | librsvg = dependency('librsvg-2.0', required : get_option('librsvg')) | ||||||
| xcursor = dependency('xcursor', required : get_option('xcursor')) | xcursor = dependency('xcursor', required : get_option('xcursor')) | ||||||
| libheif = dependency('libheif', required : get_option('libheif')) | libheif = dependency('libheif', required : get_option('libheif')) | ||||||
| libtiff = dependency('libtiff-4', required : get_option('libtiff')) | libtiff = dependency('libtiff-4', required : get_option('libtiff')) | ||||||
| # This is a direct dependency of GTK+, but its usage may be disabled for images. | # This is a direct dependency of GTK+, but its usage may be disabled. | ||||||
| gdkpixbuf = dependency('gdk-pixbuf-2.0', required : get_option('gdk-pixbuf')) | gdkpixbuf = dependency('gdk-pixbuf-2.0', required : get_option('gdk-pixbuf')) | ||||||
| dependencies = [ | dependencies = [ | ||||||
| 	dependency('gtk+-3.0'), | 	dependency('gtk+-3.0'), | ||||||
| @ -54,24 +53,6 @@ dependencies = [ | |||||||
| 	cc.find_library('m', required : false), | 	cc.find_library('m', required : false), | ||||||
| ] | ] | ||||||
| 
 | 
 | ||||||
| # As of writing, no pkg-config file is produced, and the plugin is not installed |  | ||||||
| # by default. The library can be built statically, but it's a bit of a hassle. |  | ||||||
| have_lcms2_fast_float = false |  | ||||||
| if not get_option('lcms2fastfloat').disabled() |  | ||||||
| 	lcms2ff = dependency('lcms2_fast_float', required : false) |  | ||||||
| 	if not lcms2ff.found() |  | ||||||
| 		lcms2ff = cc.find_library( |  | ||||||
| 			'lcms2_fast_float', required : get_option('lcms2fastfloat')) |  | ||||||
| 		if lcms2ff.found() and not cc.has_header('lcms2_fast_float.h') |  | ||||||
| 			error('lcms2_fast_float.h not found') |  | ||||||
| 		endif |  | ||||||
| 	endif |  | ||||||
| 	if lcms2ff.found() |  | ||||||
| 		dependencies += lcms2ff |  | ||||||
| 		have_lcms2_fast_float = true |  | ||||||
| 	endif |  | ||||||
| endif |  | ||||||
| 
 |  | ||||||
| # As of writing, the API is unstable, and no pkg-config file is produced. | # As of writing, the API is unstable, and no pkg-config file is produced. | ||||||
| # Trying to wrap Cargo in Meson is a recipe for pain, so no version pinning. | # Trying to wrap Cargo in Meson is a recipe for pain, so no version pinning. | ||||||
| have_resvg = false | have_resvg = false | ||||||
| @ -104,7 +85,6 @@ endif | |||||||
| 
 | 
 | ||||||
| conf.set('HAVE_JPEG_QS', libjpegqs.found()) | conf.set('HAVE_JPEG_QS', libjpegqs.found()) | ||||||
| conf.set('HAVE_LCMS2', lcms2.found()) | conf.set('HAVE_LCMS2', lcms2.found()) | ||||||
| conf.set('HAVE_LCMS2_FAST_FLOAT', have_lcms2_fast_float) |  | ||||||
| conf.set('HAVE_LIBRAW', libraw.found()) | conf.set('HAVE_LIBRAW', libraw.found()) | ||||||
| conf.set('HAVE_RESVG', have_resvg) | conf.set('HAVE_RESVG', have_resvg) | ||||||
| conf.set('HAVE_LIBRSVG', librsvg.found()) | conf.set('HAVE_LIBRSVG', librsvg.found()) | ||||||
| @ -256,8 +236,7 @@ if not win32 | |||||||
| 		else | 		else | ||||||
| 			command = ['env', 'LC_ALL=C', | 			command = ['env', 'LC_ALL=C', | ||||||
| 				'asciidoc-release-version=' + meson.project_version(), | 				'asciidoc-release-version=' + meson.project_version(), | ||||||
| 				'awk', '-f', files('submodules/liberty/tools/asciiman.awk'), | 				'awk', '-f', files('liberty/tools/asciiman.awk'), '@INPUT@'] | ||||||
| 				'@INPUT@'] |  | ||||||
| 			man_capture = true | 			man_capture = true | ||||||
| 		endif | 		endif | ||||||
| 		custom_target('manpage for ' + page, | 		custom_target('manpage for ' + page, | ||||||
|  | |||||||
| @ -3,8 +3,6 @@ option('tools', type : 'feature', value : 'disabled', | |||||||
| 
 | 
 | ||||||
| option('lcms2', type : 'feature', value : 'auto', | option('lcms2', type : 'feature', value : 'auto', | ||||||
| 	description : 'Build with Little CMS colour management') | 	description : 'Build with Little CMS colour management') | ||||||
| option('lcms2fastfloat', type : 'feature', value : 'auto', |  | ||||||
| 	description : 'Build with Little CMS fast float plugin support') |  | ||||||
| option('libjpegqs', type : 'feature', value : 'auto', | option('libjpegqs', type : 'feature', value : 'auto', | ||||||
| 	description : 'Build with JPEG Quant Smooth integration') | 	description : 'Build with JPEG Quant Smooth integration') | ||||||
| option('libraw', type : 'feature', value : 'auto', | option('libraw', type : 'feature', value : 'auto', | ||||||
|  | |||||||
| @ -97,7 +97,7 @@ setup() { | |||||||
| 	endian = 'little' | 	endian = 'little' | ||||||
| 	EOF | 	EOF | ||||||
| 
 | 
 | ||||||
| 	meson setup --buildtype=debugoptimized --prefix="$packagedir" \ | 	meson --buildtype=debugoptimized --prefix="$packagedir" \ | ||||||
| 		--bindir . --libdir . --cross-file="$toolchain" "$builddir" "$sourcedir" | 		--bindir . --libdir . --cross-file="$toolchain" "$builddir" "$sourcedir" | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | |||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user