Compare commits
8 Commits
f56c40cf00
...
cc59e537da
Author | SHA1 | Date | |
---|---|---|---|
cc59e537da | |||
338ae69121 | |||
1c61fcc5bc | |||
dd1d6647dc | |||
abf4f1a792 | |||
6a7c86a41b | |||
6277a32fe6 | |||
8f0576d6bc |
4
.gitmodules
vendored
4
.gitmodules
vendored
@ -1,6 +1,6 @@
|
|||||||
[submodule "wuffs-mirror-release-c"]
|
[submodule "wuffs-mirror-release-c"]
|
||||||
path = wuffs-mirror-release-c
|
path = submodules/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 = liberty
|
path = submodules/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 (that said, macOS and Windows ports are possible).
|
for Linux (as well as macOS and Windows, though these have known issues).
|
||||||
|
|
||||||
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,15 +40,16 @@ 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:
|
Runtime dependencies: gtk+-3.0, glib>=2.64, pixman-1, shared-mime-info,
|
||||||
gtk+-3.0, glib>=2.64, pixman-1, shared-mime-info, libturbojpeg, libwebp +
|
libturbojpeg, libwebp, librsvg-2.0 (for icons) +
|
||||||
Optional dependencies: lcms2, LibRaw, librsvg-2.0, xcursor, libheif, libtiff,
|
Optional dependencies: lcms2, Little CMS fast float plugin,
|
||||||
ExifTool, resvg (unstable API, needs to be requested explicitly) +
|
LibRaw, librsvg-2.0, xcursor, libheif, libtiff, ExifTool,
|
||||||
|
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 builddir
|
$ meson setup builddir
|
||||||
$ cd builddir
|
$ cd builddir
|
||||||
$ meson compile
|
$ meson compile
|
||||||
|
|
||||||
|
@ -640,11 +640,15 @@ 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.
|
||||||
GtkIconInfo *icon_info = gtk_icon_theme_lookup_by_gicon(
|
GtkIconTheme *theme = gtk_icon_theme_get_default();
|
||||||
gtk_icon_theme_get_default(), entry->icon, self->item_height / 2,
|
GtkIconInfo *icon_info = gtk_icon_theme_lookup_by_gicon(theme, entry->icon,
|
||||||
GTK_ICON_LOOKUP_FORCE_SYMBOLIC);
|
self->item_height / 2, 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 "wuffs-mirror-release-c/release/c/wuffs-v0.3.c"
|
#include "submodules/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,6 +43,11 @@
|
|||||||
#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);
|
||||||
@ -388,12 +393,6 @@ 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);
|
||||||
@ -419,11 +418,6 @@ 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;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2098,163 +2092,12 @@ static const char stylesheet[] = "@define-color fiv-tile @content_view_bg; \
|
|||||||
} \
|
} \
|
||||||
.fiv-information label { padding: 0 4px; }";
|
.fiv-information label { padding: 0 4px; }";
|
||||||
|
|
||||||
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
|
static void
|
||||||
output_thumbnail_for_search(gchar **uris, const char *size_arg)
|
on_app_startup(GApplication *app, G_GNUC_UNUSED gpointer user_data)
|
||||||
{
|
{
|
||||||
FivThumbnailSize size = output_thumbnail_prologue(uris, size_arg);
|
// We can't prevent GApplication from adding --gapplication-service.
|
||||||
|
if (g_application_get_flags(app) & G_APPLICATION_IS_SERVICE)
|
||||||
GError *error = NULL;
|
exit(EXIT_FAILURE);
|
||||||
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.
|
||||||
@ -2357,9 +2200,9 @@ main(int argc, char *argv[])
|
|||||||
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_window_new(GTK_WINDOW_TOPLEVEL);
|
g.window = gtk_application_window_new(GTK_APPLICATION(app));
|
||||||
g_signal_connect(g.window, "destroy",
|
g_signal_connect_swapped(g.window, "destroy",
|
||||||
G_CALLBACK(gtk_main_quit), NULL);
|
G_CALLBACK(g_application_quit), app);
|
||||||
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",
|
||||||
@ -2410,24 +2253,34 @@ main(int argc, char *argv[])
|
|||||||
|
|
||||||
// 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 (args) {
|
if (o.args) {
|
||||||
const gchar *target = *args;
|
const gchar *target = *o.args;
|
||||||
if (args[1]) {
|
if (o.args[1]) {
|
||||||
fiv_collection_reload(args);
|
fiv_collection_reload(o.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, browse);
|
open_any_file(file, o.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);
|
||||||
@ -2435,6 +2288,184 @@ main(int argc, char *argv[])
|
|||||||
}
|
}
|
||||||
|
|
||||||
gtk_widget_show(g.window);
|
gtk_widget_show(g.window);
|
||||||
gtk_main();
|
}
|
||||||
return 0;
|
|
||||||
|
// --- 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;
|
||||||
|
}
|
||||||
|
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,11 +25,12 @@ 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.
|
# This is a direct dependency of GTK+, but its usage may be disabled for images.
|
||||||
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'),
|
||||||
@ -53,6 +54,24 @@ 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
|
||||||
@ -85,6 +104,7 @@ 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())
|
||||||
@ -236,7 +256,8 @@ 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('liberty/tools/asciiman.awk'), '@INPUT@']
|
'awk', '-f', files('submodules/liberty/tools/asciiman.awk'),
|
||||||
|
'@INPUT@']
|
||||||
man_capture = true
|
man_capture = true
|
||||||
endif
|
endif
|
||||||
custom_target('manpage for ' + page,
|
custom_target('manpage for ' + page,
|
||||||
|
@ -3,6 +3,8 @@ 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 --buildtype=debugoptimized --prefix="$packagedir" \
|
meson setup --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