Compare commits

..

No commits in common. "701846ab398371de5a921b1a561bcc1601cd8297" and "e6341e59bbc248b84a33aef263c90d6eb97e9302" have entirely different histories.

14 changed files with 60 additions and 1133 deletions

View File

@ -29,7 +29,6 @@
#endif // GDK_WINDOWING_QUARTZ
#include "fiv-browser.h"
#include "fiv-collection.h"
#include "fiv-context-menu.h"
#include "fiv-io.h"
#include "fiv-thumbnail.h"
@ -103,7 +102,6 @@ static cairo_user_data_key_t fiv_browser_key_mtime_msec;
struct entry {
gchar *uri; ///< GIO URI
gchar *target_uri; ///< GIO URI for any target
gint64 mtime_msec; ///< Modification time in milliseconds
cairo_surface_t *thumbnail; ///< Prescaled thumbnail
GIcon *icon; ///< If no thumbnail, use this icon
@ -113,7 +111,6 @@ static void
entry_free(Entry *self)
{
g_free(self->uri);
g_free(self->target_uri);
g_clear_pointer(&self->thumbnail, cairo_surface_destroy);
g_clear_object(&self->icon);
}
@ -440,17 +437,6 @@ rescale_thumbnail(cairo_surface_t *thumbnail, double row_height)
return scaled;
}
static char *
entry_system_wide_uri(const Entry *self)
{
// "recent" and "trash", e.g., also have "standard::target-uri" set,
// but we'd like to avoid saving their thumbnails.
if (self->target_uri && fiv_collection_uri_matches(self->uri))
return self->target_uri;
return self->uri;
}
static void
entry_add_thumbnail(gpointer data, gpointer user_data)
{
@ -470,7 +456,7 @@ entry_add_thumbnail(gpointer data, gpointer user_data)
// unnecessarily; we might also shift the concern there).
} else {
cairo_surface_t *found = fiv_thumbnail_lookup(
entry_system_wide_uri(self), self->mtime_msec, browser->item_size);
self->uri, self->mtime_msec, browser->item_size);
self->thumbnail = rescale_thumbnail(found, browser->item_height);
}
@ -603,8 +589,7 @@ thumbnailer_reprocess_entry(FivBrowser *self, GBytes *output, Entry *entry)
g_list_append(self->thumbnailers_queue, entry);
}
// This choice of mtime favours unnecessary thumbnail reloading
// over retaining stale data.
// This choice of mtime favours unnecessary thumbnail reloading.
cairo_surface_set_user_data(entry->thumbnail,
&fiv_browser_key_mtime_msec, (void *) (intptr_t) entry->mtime_msec,
NULL);
@ -678,13 +663,12 @@ thumbnailer_next(Thumbnailer *t)
// - We've found one, but we're not quite happy with it:
// always run the full process for a high-quality wide thumbnail.
// - We can't end up here in any other cases.
const char *uri = entry_system_wide_uri(t->target);
const char *argv_faster[] = {PROJECT_NAME, "--extract-thumbnail",
"--thumbnail", fiv_thumbnail_sizes[self->item_size].thumbnail_spec_name,
"--", uri, NULL};
"--", t->target->uri, NULL};
const char *argv_slower[] = {PROJECT_NAME,
"--thumbnail", fiv_thumbnail_sizes[self->item_size].thumbnail_spec_name,
"--", uri, NULL};
"--", t->target->uri, NULL};
GError *error = NULL;
t->minion = g_subprocess_newv(t->target->icon ? argv_faster : argv_slower,
@ -1275,7 +1259,7 @@ fiv_browser_drag_data_get(GtkWidget *widget,
FivBrowser *self = FIV_BROWSER(widget);
if (self->selected) {
(void) gtk_selection_data_set_uris(
data, (gchar *[]) {entry_system_wide_uri(self->selected), NULL});
data, (gchar *[]){self->selected->uri, NULL});
}
}
@ -1482,9 +1466,10 @@ fiv_browser_query_tooltip(GtkWidget *widget, gint x, gint y,
return FALSE;
GFile *file = g_file_new_for_uri(entry->uri);
GFileInfo *info =
g_file_query_info(file, G_FILE_ATTRIBUTE_STANDARD_DISPLAY_NAME,
G_FILE_QUERY_INFO_NONE, NULL, NULL);
GFileInfo *info = g_file_query_info(file,
G_FILE_ATTRIBUTE_STANDARD_NAME
"," G_FILE_ATTRIBUTE_STANDARD_DISPLAY_NAME,
G_FILE_QUERY_INFO_NONE, NULL, NULL);
g_object_unref(file);
if (!info)
return FALSE;
@ -1755,11 +1740,8 @@ on_model_files_changed(FivIoModel *model, FivBrowser *self)
gsize len = 0;
const FivIoModelEntry *files = fiv_io_model_get_files(self->model, &len);
for (gsize i = 0; i < len; i++) {
Entry e = {.thumbnail = NULL,
.uri = g_strdup(files[i].uri),
.target_uri = g_strdup(files[i].target_uri),
.mtime_msec = files[i].mtime_msec};
g_array_append_val(self->entries, e);
g_array_append_val(self->entries, ((Entry) {.thumbnail = NULL,
.uri = g_strdup(files[i].uri), .mtime_msec = files[i].mtime_msec}));
}
fiv_browser_select(self, selected_uri);

View File

@ -1,725 +0,0 @@
//
// fiv-collection.c: GVfs extension for grouping arbitrary files together
//
// Copyright (c) 2022, Přemysl Eric Janouch <p@janouch.name>
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted.
//
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
// SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
// OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
// CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
//
#include <gio/gio.h>
#include "fiv-collection.h"
static struct {
GFile **files;
gsize files_len;
} g;
gboolean
fiv_collection_uri_matches(const char *uri)
{
static const char prefix[] = FIV_COLLECTION_SCHEME ":";
return !g_ascii_strncasecmp(uri, prefix, sizeof prefix - 1);
}
GFile **
fiv_collection_get_contents(gsize *len)
{
*len = g.files_len;
return g.files;
}
void
fiv_collection_reload(gchar **uris)
{
if (g.files) {
for (gsize i = 0; i < g.files_len; i++)
g_object_unref(g.files[i]);
g_free(g.files);
}
g.files_len = g_strv_length(uris);
g.files = g_malloc0_n(g.files_len + 1, sizeof *g.files);
for (gsize i = 0; i < g.files_len; i++)
g.files[i] = g_file_new_for_uri(uris[i]);
}
// --- Declarations ------------------------------------------------------------
#define FIV_TYPE_COLLECTION_FILE (fiv_collection_file_get_type())
G_DECLARE_FINAL_TYPE(
FivCollectionFile, fiv_collection_file, FIV, COLLECTION_FILE, GObject)
struct _FivCollectionFile {
GObject parent_instance;
gint index; ///< Original index into g.files, or -1
GFile *target; ///< The wrapped file, or NULL for root
gchar *subpath; ///< Any subpath, rooted at the target
};
#define FIV_TYPE_COLLECTION_ENUMERATOR (fiv_collection_enumerator_get_type())
G_DECLARE_FINAL_TYPE(FivCollectionEnumerator, fiv_collection_enumerator, FIV,
COLLECTION_ENUMERATOR, GFileEnumerator)
struct _FivCollectionEnumerator {
GFileEnumerator parent_instance;
gchar *attributes; ///< Attributes to look up
gsize index; ///< Root: index into g.files
GFileEnumerator *subenumerator; ///< Non-root: a wrapped enumerator
};
// --- Enumerator --------------------------------------------------------------
G_DEFINE_TYPE(
FivCollectionEnumerator, fiv_collection_enumerator, G_TYPE_FILE_ENUMERATOR)
static void
fiv_collection_enumerator_finalize(GObject *object)
{
FivCollectionEnumerator *self = FIV_COLLECTION_ENUMERATOR(object);
g_free(self->attributes);
g_clear_object(&self->subenumerator);
}
static GFileInfo *
fiv_collection_enumerator_next_file(GFileEnumerator *enumerator,
GCancellable *cancellable, GError **error)
{
FivCollectionEnumerator *self = FIV_COLLECTION_ENUMERATOR(enumerator);
if (self->subenumerator) {
GFileInfo *info = g_file_enumerator_next_file(
self->subenumerator, cancellable, error);
if (!info)
return NULL;
// TODO(p): Consider discarding certain classes of attributes
// from the results (adjusting "attributes" is generally unreliable).
GFile *target = g_file_enumerator_get_child(self->subenumerator, info);
gchar *target_uri = g_file_get_uri(target);
g_object_unref(target);
g_file_info_set_attribute_string(
info, G_FILE_ATTRIBUTE_STANDARD_TARGET_URI, target_uri);
g_free(target_uri);
return info;
}
if (self->index >= g.files_len)
return NULL;
FivCollectionFile *file = g_object_new(FIV_TYPE_COLLECTION_FILE, NULL);
file->index = self->index;
file->target = g_object_ref(g.files[self->index++]);
GFileInfo *info = g_file_query_info(G_FILE(file), self->attributes,
G_FILE_QUERY_INFO_NONE, cancellable, error);
g_object_unref(file);
return info;
}
static gboolean
fiv_collection_enumerator_close(
GFileEnumerator *enumerator, GCancellable *cancellable, GError **error)
{
FivCollectionEnumerator *self = FIV_COLLECTION_ENUMERATOR(enumerator);
if (self->subenumerator)
return g_file_enumerator_close(self->subenumerator, cancellable, error);
return TRUE;
}
static void
fiv_collection_enumerator_class_init(FivCollectionEnumeratorClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS(klass);
object_class->finalize = fiv_collection_enumerator_finalize;
GFileEnumeratorClass *enumerator_class = G_FILE_ENUMERATOR_CLASS(klass);
enumerator_class->next_file = fiv_collection_enumerator_next_file;
enumerator_class->close_fn = fiv_collection_enumerator_close;
}
static void
fiv_collection_enumerator_init(G_GNUC_UNUSED FivCollectionEnumerator *self)
{
}
// --- Proxying GFile implementation -------------------------------------------
static void fiv_collection_file_file_iface_init(GFileIface *iface);
G_DEFINE_TYPE_WITH_CODE(FivCollectionFile, fiv_collection_file, G_TYPE_OBJECT,
G_IMPLEMENT_INTERFACE(G_TYPE_FILE, fiv_collection_file_file_iface_init))
static void
fiv_collection_file_finalize(GObject *object)
{
FivCollectionFile *self = FIV_COLLECTION_FILE(object);
if (self->target)
g_object_unref(self->target);
g_free(self->subpath);
}
static GFile *
fiv_collection_file_dup(GFile *file)
{
FivCollectionFile *self = FIV_COLLECTION_FILE(file);
FivCollectionFile *new = g_object_new(FIV_TYPE_COLLECTION_FILE, NULL);
if (self->target)
new->target = g_object_ref(self->target);
new->subpath = g_strdup(self->subpath);
return G_FILE(new);
}
static guint
fiv_collection_file_hash(GFile *file)
{
FivCollectionFile *self = FIV_COLLECTION_FILE(file);
guint hash = g_int_hash(&self->index);
if (self->target)
hash ^= g_file_hash(self->target);
if (self->subpath)
hash ^= g_str_hash(self->subpath);
return hash;
}
static gboolean
fiv_collection_file_equal(GFile *file1, GFile *file2)
{
FivCollectionFile *cf1 = FIV_COLLECTION_FILE(file1);
FivCollectionFile *cf2 = FIV_COLLECTION_FILE(file2);
return cf1->index == cf2->index && cf1->target == cf2->target &&
!g_strcmp0(cf1->subpath, cf2->subpath);
}
static gboolean
fiv_collection_file_is_native(G_GNUC_UNUSED GFile *file)
{
return FALSE;
}
static gboolean
fiv_collection_file_has_uri_scheme(
G_GNUC_UNUSED GFile *file, const char *uri_scheme)
{
return !g_ascii_strcasecmp(uri_scheme, FIV_COLLECTION_SCHEME);
}
static char *
fiv_collection_file_get_uri_scheme(G_GNUC_UNUSED GFile *file)
{
return g_strdup(FIV_COLLECTION_SCHEME);
}
static char *
get_prefixed_name(FivCollectionFile *self, const char *name)
{
return g_strdup_printf("%d. %s", self->index + 1, name);
}
static char *
get_target_basename(FivCollectionFile *self)
{
g_return_val_if_fail(self->target != NULL, g_strdup(""));
// The "http" scheme doesn't behave nicely, make something up if needed.
// Foreign roots likewise need to be fixed up for our needs.
gchar *basename = g_file_get_basename(self->target);
if (!basename || *basename == '/') {
g_free(basename);
basename = g_file_get_uri_scheme(self->target);
}
gchar *name = get_prefixed_name(self, basename);
g_free(basename);
return name;
}
static char *
fiv_collection_file_get_basename(GFile *file)
{
FivCollectionFile *self = FIV_COLLECTION_FILE(file);
if (!self->target)
return g_strdup("/");
if (self->subpath)
return g_path_get_basename(self->subpath);
return get_target_basename(self);
}
static char *
fiv_collection_file_get_path(G_GNUC_UNUSED GFile *file)
{
// This doesn't seem to be worth implementing (for compatible targets).
return NULL;
}
static char *
get_unescaped_uri(FivCollectionFile *self)
{
GString *unescaped = g_string_new(FIV_COLLECTION_SCHEME ":/");
if (!self->target)
return g_string_free(unescaped, FALSE);
gchar *basename = get_target_basename(self);
g_string_append(unescaped, basename);
g_free(basename);
if (self->subpath)
g_string_append(g_string_append(unescaped, "/"), self->subpath);
return g_string_free(unescaped, FALSE);
}
static char *
fiv_collection_file_get_uri(GFile *file)
{
gchar *unescaped = get_unescaped_uri(FIV_COLLECTION_FILE(file));
gchar *uri = g_uri_escape_string(
unescaped, G_URI_RESERVED_CHARS_ALLOWED_IN_PATH, FALSE);
g_free(unescaped);
return uri;
}
static char *
fiv_collection_file_get_parse_name(GFile *file)
{
gchar *unescaped = get_unescaped_uri(FIV_COLLECTION_FILE(file));
gchar *parse_name = g_uri_escape_string(
unescaped, G_URI_RESERVED_CHARS_ALLOWED_IN_PATH " ", TRUE);
g_free(unescaped);
return parse_name;
}
static GFile *
fiv_collection_file_get_parent(GFile *file)
{
FivCollectionFile *self = FIV_COLLECTION_FILE(file);
if (!self->target)
return NULL;
FivCollectionFile *new = g_object_new(FIV_TYPE_COLLECTION_FILE, NULL);
if (self->subpath) {
new->index = self->index;
new->target = g_object_ref(self->target);
if (strchr(self->subpath, '/'))
new->subpath = g_path_get_dirname(self->subpath);
}
return G_FILE(new);
}
static gboolean
fiv_collection_file_prefix_matches(GFile *prefix, GFile *file)
{
FivCollectionFile *self = FIV_COLLECTION_FILE(file);
FivCollectionFile *parent = FIV_COLLECTION_FILE(prefix);
// The root has no parents.
if (!self->target)
return FALSE;
// The root prefixes everything that is not the root.
if (!parent->target)
return TRUE;
if (self->index != parent->index || !self->subpath)
return FALSE;
if (!parent->subpath)
return TRUE;
return g_str_has_prefix(self->subpath, parent->subpath) &&
self->subpath[strlen(parent->subpath)] == '/';
}
// This virtual method seems to be intended for local files only,
// and documentation claims that the result is in filesystem encoding.
// For us, paths are mostly opaque strings of arbitrary encoding, however.
static char *
fiv_collection_file_get_relative_path(GFile *parent, GFile *descendant)
{
FivCollectionFile *self = FIV_COLLECTION_FILE(descendant);
FivCollectionFile *prefix = FIV_COLLECTION_FILE(parent);
if (!fiv_collection_file_prefix_matches(parent, descendant))
return NULL;
g_assert((!prefix->target && self->target) ||
(prefix->target && self->target && self->subpath));
if (!prefix->target) {
gchar *basename = get_target_basename(self);
gchar *path = g_build_path("/", basename, self->subpath, NULL);
g_free(basename);
return path;
}
return prefix->subpath
? g_strdup(self->subpath + strlen(prefix->subpath) + 1)
: g_strdup(self->subpath);
}
static GFile *
get_file_for_path(const char *path)
{
// Skip all initial slashes, making the result relative to the root.
if (!*(path += strspn(path, "/")))
return g_object_new(FIV_TYPE_COLLECTION_FILE, NULL);
char *end = NULL;
guint64 i = g_ascii_strtoull(path, &end, 10);
if (i <= 0 || i > g.files_len || *end != '.')
return g_file_new_for_uri("");
FivCollectionFile *new = g_object_new(FIV_TYPE_COLLECTION_FILE, NULL);
new->index = --i;
new->target = g_object_ref(g.files[i]);
const char *subpath = strchr(path, '/');
if (subpath && subpath[1])
new->subpath = g_strdup(++subpath);
return G_FILE(new);
}
static GFile *
fiv_collection_file_resolve_relative_path(
GFile *file, const char *relative_path)
{
FivCollectionFile *self = FIV_COLLECTION_FILE(file);
if (!self->target)
return get_file_for_path(relative_path);
gchar *basename = get_target_basename(self);
gchar *root = g_build_path("/", "/", basename, self->subpath, NULL);
g_free(basename);
gchar *canonicalized = g_canonicalize_filename(relative_path, root);
GFile *result = get_file_for_path(canonicalized);
g_free(canonicalized);
return result;
}
static GFile *
get_target_subpathed(FivCollectionFile *self)
{
return self->subpath
? g_file_resolve_relative_path(self->target, self->subpath)
: g_object_ref(self->target);
}
static GFile *
fiv_collection_file_get_child_for_display_name(
GFile *file, const char *display_name, GError **error)
{
FivCollectionFile *self = FIV_COLLECTION_FILE(file);
if (!self->target)
return get_file_for_path(display_name);
// Implementations often redirect to g_file_resolve_relative_path().
// We don't want to go up (and possibly receive a "/" basename),
// nor do we want to skip path elements.
// TODO(p): This should still be implementable, via URI inspection.
if (strchr(display_name, '/')) {
g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT,
"Display name must not contain path separators");
return NULL;
}
GFile *intermediate = get_target_subpathed(self);
GFile *resolved =
g_file_get_child_for_display_name(intermediate, display_name, error);
g_object_unref(intermediate);
if (!resolved)
return NULL;
// Try to retrieve the display name converted to whatever insanity
// the target might have chosen to encode its paths with.
gchar *converted = g_file_get_basename(resolved);
g_object_unref(resolved);
FivCollectionFile *new = g_object_new(FIV_TYPE_COLLECTION_FILE, NULL);
new->index = self->index;
new->target = g_object_ref(self->target);
new->subpath = self->subpath
? g_build_path("/", self->subpath, converted, NULL)
: g_strdup(converted);
g_free(converted);
return G_FILE(new);
}
static GFileEnumerator *
fiv_collection_file_enumerate_children(GFile *file, const char *attributes,
GFileQueryInfoFlags flags, GCancellable *cancellable, GError **error)
{
FivCollectionFile *self = FIV_COLLECTION_FILE(file);
FivCollectionEnumerator *enumerator = g_object_new(
FIV_TYPE_COLLECTION_ENUMERATOR, "container", file, NULL);
enumerator->attributes = g_strdup(attributes);
if (self->target) {
GFile *intermediate = get_target_subpathed(self);
enumerator->subenumerator = g_file_enumerate_children(
intermediate, enumerator->attributes, flags, cancellable, error);
g_object_unref(intermediate);
}
return G_FILE_ENUMERATOR(enumerator);
}
// TODO(p): Implement async variants of this proxying method.
static GFileInfo *
fiv_collection_file_query_info(GFile *file, const char *attributes,
GFileQueryInfoFlags flags, GCancellable *cancellable,
G_GNUC_UNUSED GError **error)
{
FivCollectionFile *self = FIV_COLLECTION_FILE(file);
GError *e = NULL;
if (!self->target) {
GFileInfo *info = g_file_info_new();
g_file_info_set_file_type(info, G_FILE_TYPE_DIRECTORY);
g_file_info_set_name(info, "/");
g_file_info_set_display_name(info, "Collection");
GIcon *icon = g_icon_new_for_string("shapes-symbolic", NULL);
if (icon) {
g_file_info_set_symbolic_icon(info, icon);
g_object_unref(icon);
} else {
g_warning("%s", e->message);
g_error_free(e);
}
return info;
}
// The "http" scheme doesn't behave nicely, make something up if needed.
GFile *intermediate = get_target_subpathed(self);
GFileInfo *info =
g_file_query_info(intermediate, attributes, flags, cancellable, &e);
if (!info) {
g_warning("%s", e->message);
g_error_free(e);
info = g_file_info_new();
g_file_info_set_file_type(info, G_FILE_TYPE_REGULAR);
gchar *basename = g_file_get_basename(intermediate);
g_file_info_set_name(info, basename);
// The display name is "guaranteed to always be set" when queried,
// which is up to implementations.
gchar *safe = g_utf8_make_valid(basename, -1);
g_free(basename);
g_file_info_set_display_name(info, safe);
g_free(safe);
}
gchar *target_uri = g_file_get_uri(intermediate);
g_file_info_set_attribute_string(
info, G_FILE_ATTRIBUTE_STANDARD_TARGET_URI, target_uri);
g_free(target_uri);
g_object_unref(intermediate);
// Ensure all basenames that might have been set have the numeric prefix.
const char *name = NULL;
if (!self->subpath) {
// Always set this, because various schemes may not do so themselves,
// which then troubles GFileEnumerator.
gchar *basename = get_target_basename(self);
g_file_info_set_name(info, basename);
g_free(basename);
if ((name = g_file_info_get_display_name(info))) {
gchar *prefixed = get_prefixed_name(self, name);
g_file_info_set_display_name(info, prefixed);
g_free(prefixed);
}
if ((name = g_file_info_get_edit_name(info))) {
gchar *prefixed = get_prefixed_name(self, name);
g_file_info_set_edit_name(info, prefixed);
g_free(prefixed);
}
}
return info;
}
static GFileInfo *
fiv_collection_file_query_filesystem_info(G_GNUC_UNUSED GFile *file,
G_GNUC_UNUSED const char *attributes,
G_GNUC_UNUSED GCancellable *cancellable, G_GNUC_UNUSED GError **error)
{
GFileInfo *info = g_file_info_new();
GFileAttributeMatcher *matcher = g_file_attribute_matcher_new(attributes);
if (g_file_attribute_matcher_matches(
matcher, G_FILE_ATTRIBUTE_FILESYSTEM_TYPE)) {
g_file_info_set_attribute_string(
info, G_FILE_ATTRIBUTE_FILESYSTEM_TYPE, FIV_COLLECTION_SCHEME);
}
if (g_file_attribute_matcher_matches(
matcher, G_FILE_ATTRIBUTE_FILESYSTEM_READONLY)) {
g_file_info_set_attribute_boolean(
info, G_FILE_ATTRIBUTE_FILESYSTEM_READONLY, TRUE);
}
g_file_attribute_matcher_unref(matcher);
return info;
}
static GFile *
fiv_collection_file_set_display_name(G_GNUC_UNUSED GFile *file,
G_GNUC_UNUSED const char *display_name,
G_GNUC_UNUSED GCancellable *cancellable, GError **error)
{
g_set_error_literal(
error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "Operation not supported");
return NULL;
}
static GFileInputStream *
fiv_collection_file_read(GFile *file, GCancellable *cancellable, GError **error)
{
FivCollectionFile *self = FIV_COLLECTION_FILE(file);
if (!self->target) {
g_set_error_literal(
error, G_IO_ERROR, G_IO_ERROR_IS_DIRECTORY, "Is a directory");
return NULL;
}
GFile *intermediate = get_target_subpathed(self);
GFileInputStream *stream = g_file_read(intermediate, cancellable, error);
g_object_unref(intermediate);
return stream;
}
static void
on_read(GObject *source_object, GAsyncResult *res, gpointer user_data)
{
GFile *intermediate = G_FILE(source_object);
GTask *task = G_TASK(user_data);
GError *error = NULL;
GFileInputStream *result = g_file_read_finish(intermediate, res, &error);
if (result)
g_task_return_pointer(task, result, g_object_unref);
else
g_task_return_error(task, error);
g_object_unref(task);
}
static void
fiv_collection_file_read_async(GFile *file, int io_priority,
GCancellable *cancellable, GAsyncReadyCallback callback, gpointer user_data)
{
FivCollectionFile *self = FIV_COLLECTION_FILE(file);
GTask *task = g_task_new(file, cancellable, callback, user_data);
g_task_set_name(task, __func__);
g_task_set_priority(task, io_priority);
if (!self->target) {
g_task_return_new_error(
task, G_IO_ERROR, G_IO_ERROR_IS_DIRECTORY, "Is a directory");
g_object_unref(task);
return;
}
GFile *intermediate = get_target_subpathed(self);
g_file_read_async(intermediate, io_priority, cancellable, on_read, task);
g_object_unref(intermediate);
}
static GFileInputStream *
fiv_collection_file_read_finish(
G_GNUC_UNUSED GFile *file, GAsyncResult *res, GError **error)
{
return g_task_propagate_pointer(G_TASK(res), error);
}
static void
fiv_collection_file_file_iface_init(GFileIface *iface)
{
// Required methods that would segfault if unimplemented.
iface->dup = fiv_collection_file_dup;
iface->hash = fiv_collection_file_hash;
iface->equal = fiv_collection_file_equal;
iface->is_native = fiv_collection_file_is_native;
iface->has_uri_scheme = fiv_collection_file_has_uri_scheme;
iface->get_uri_scheme = fiv_collection_file_get_uri_scheme;
iface->get_basename = fiv_collection_file_get_basename;
iface->get_path = fiv_collection_file_get_path;
iface->get_uri = fiv_collection_file_get_uri;
iface->get_parse_name = fiv_collection_file_get_parse_name;
iface->get_parent = fiv_collection_file_get_parent;
iface->prefix_matches = fiv_collection_file_prefix_matches;
iface->get_relative_path = fiv_collection_file_get_relative_path;
iface->resolve_relative_path = fiv_collection_file_resolve_relative_path;
iface->get_child_for_display_name =
fiv_collection_file_get_child_for_display_name;
iface->set_display_name = fiv_collection_file_set_display_name;
// Optional methods.
iface->enumerate_children = fiv_collection_file_enumerate_children;
iface->query_info = fiv_collection_file_query_info;
iface->query_filesystem_info = fiv_collection_file_query_filesystem_info;
iface->read_fn = fiv_collection_file_read;
iface->read_async = fiv_collection_file_read_async;
iface->read_finish = fiv_collection_file_read_finish;
iface->supports_thread_contexts = TRUE;
}
static void
fiv_collection_file_class_init(FivCollectionFileClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS(klass);
object_class->finalize = fiv_collection_file_finalize;
}
static void
fiv_collection_file_init(FivCollectionFile *self)
{
self->index = -1;
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
static GFile *
get_file_for_uri(G_GNUC_UNUSED GVfs *vfs, const char *identifier,
G_GNUC_UNUSED gpointer user_data)
{
static const char prefix[] = FIV_COLLECTION_SCHEME ":";
const char *path = identifier + sizeof prefix - 1;
if (!g_str_has_prefix(identifier, prefix))
return NULL;
// Specifying the authority is not supported.
if (g_str_has_prefix(path, "//"))
return NULL;
// Otherwise, it needs to look like an absolute path.
if (!g_str_has_prefix(path, "/"))
return NULL;
// TODO(p): Figure out what to do about queries and fragments.
// GDummyFile carries them across level, which seems rather arbitrary.
const char *trailing = strpbrk(path, "?#");
gchar *unescaped = g_uri_unescape_segment(path, trailing, "/");
if (!unescaped)
return NULL;
GFile *result = get_file_for_path(unescaped);
g_free(unescaped);
return result;
}
static GFile *
parse_name(GVfs *vfs, const char *identifier, gpointer user_data)
{
// get_file_for_uri() already parses a superset of URIs.
return get_file_for_uri(vfs, identifier, user_data);
}
void
fiv_collection_register(void)
{
GVfs *vfs = g_vfs_get_default();
if (!g_vfs_register_uri_scheme(vfs, FIV_COLLECTION_SCHEME,
get_file_for_uri, NULL, NULL, parse_name, NULL, NULL))
g_warning(FIV_COLLECTION_SCHEME " scheme registration failed");
}

View File

@ -1,25 +0,0 @@
//
// fiv-collection.h: GVfs extension for grouping arbitrary files together
//
// Copyright (c) 2022, Přemysl Eric Janouch <p@janouch.name>
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted.
//
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
// SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
// OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
// CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
//
#include <gio/gio.h>
#define FIV_COLLECTION_SCHEME "collection"
gboolean fiv_collection_uri_matches(const char *uri);
GFile **fiv_collection_get_contents(gsize *len);
void fiv_collection_reload(gchar **uris);
void fiv_collection_register(void);

View File

@ -17,7 +17,6 @@
#include "config.h"
#include "fiv-collection.h"
#include "fiv-context-menu.h"
G_DEFINE_QUARK(fiv-context-menu-cancellable-quark, fiv_context_menu_cancellable)
@ -277,7 +276,7 @@ fiv_context_menu_information(GtkWindow *parent, const char *uri)
gtk_window_set_default_size(GTK_WINDOW(dialog), 600, 800);
gtk_widget_show_all(dialog);
// Mostly to identify URIs with no local path--we pipe these into ExifTool.
// Mostly for URIs with no local path--we pipe these into ExifTool.
GFile *file = g_file_new_for_uri(uri);
gchar *parse_name = g_file_get_parse_name(file);
gtk_header_bar_set_subtitle(
@ -424,10 +423,9 @@ GtkMenu *
fiv_context_menu_new(GtkWidget *widget, GFile *file)
{
GFileInfo *info = g_file_query_info(file,
G_FILE_ATTRIBUTE_STANDARD_TYPE
"," G_FILE_ATTRIBUTE_STANDARD_NAME
"," G_FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE
"," G_FILE_ATTRIBUTE_STANDARD_TARGET_URI,
G_FILE_ATTRIBUTE_STANDARD_NAME
"," G_FILE_ATTRIBUTE_STANDARD_TYPE
"," G_FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE,
G_FILE_QUERY_INFO_NONE, NULL, NULL);
if (!info)
return NULL;
@ -439,15 +437,9 @@ fiv_context_menu_new(GtkWidget *widget, GFile *file)
// This will have no application pre-assigned, for use with GTK+'s dialog.
OpenContext *ctx = g_rc_box_alloc0(sizeof *ctx);
g_weak_ref_init(&ctx->window, window);
if (!(ctx->content_type = g_strdup(g_file_info_get_content_type(info))))
ctx->content_type = g_content_type_guess(NULL, NULL, 0, NULL);
GFileType type = g_file_info_get_file_type(info);
const char *target_uri = g_file_info_get_attribute_string(
info, G_FILE_ATTRIBUTE_STANDARD_TARGET_URI);
ctx->file = target_uri && g_file_has_uri_scheme(file, FIV_COLLECTION_SCHEME)
? g_file_new_for_uri(target_uri)
: g_object_ref(file);
ctx->file = g_object_ref(file);
ctx->content_type = g_strdup(g_file_info_get_content_type(info));
gboolean regular = g_file_info_get_file_type(info) == G_FILE_TYPE_REGULAR;
g_object_unref(info);
GAppInfo *default_ =
@ -491,7 +483,7 @@ fiv_context_menu_new(GtkWidget *widget, GFile *file)
ctx, open_context_unref, 0);
gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
if (type == G_FILE_TYPE_REGULAR) {
if (regular) {
gtk_menu_shell_append(
GTK_MENU_SHELL(menu), gtk_separator_menu_item_new());

View File

@ -1892,15 +1892,14 @@ open_resvg(
resvg_options *opt = resvg_options_create();
resvg_options_load_system_fonts(opt);
if (base_file)
resvg_options_set_resources_dir(opt, g_file_peek_path(base_file));
resvg_options_set_resources_dir(opt, g_file_peek_path(base_file));
if (ctx->screen_dpi)
resvg_options_set_dpi(opt, ctx->screen_dpi);
resvg_render_tree *tree = NULL;
int err = resvg_parse_tree_from_data(data, len, opt, &tree);
resvg_options_destroy(opt);
g_clear_object(&base_file);
g_object_unref(base_file);
if (err != RESVG_OK) {
set_error(error, load_resvg_error(err));
return NULL;
@ -2964,7 +2963,6 @@ static void
model_entry_finalize(FivIoModelEntry *entry)
{
g_free(entry->uri);
g_free(entry->target_uri);
g_free(entry->collate_key);
}
@ -3084,12 +3082,9 @@ model_reload(FivIoModel *self, GError **error)
g_array_set_size(self->files, 0);
GFileEnumerator *enumerator = g_file_enumerate_children(self->directory,
G_FILE_ATTRIBUTE_STANDARD_TYPE ","
G_FILE_ATTRIBUTE_STANDARD_NAME ","
G_FILE_ATTRIBUTE_STANDARD_TARGET_URI ","
G_FILE_ATTRIBUTE_STANDARD_NAME "," G_FILE_ATTRIBUTE_STANDARD_TYPE ","
G_FILE_ATTRIBUTE_STANDARD_IS_HIDDEN ","
G_FILE_ATTRIBUTE_TIME_MODIFIED ","
G_FILE_ATTRIBUTE_TIME_MODIFIED_USEC,
G_FILE_ATTRIBUTE_TIME_MODIFIED "," G_FILE_ATTRIBUTE_TIME_MODIFIED_USEC,
G_FILE_QUERY_INFO_NONE, NULL, error);
if (!enumerator) {
// Note that this has had a side-effect of clearing all entries.
@ -3100,23 +3095,12 @@ model_reload(FivIoModel *self, GError **error)
GFileInfo *info = NULL;
GFile *child = NULL;
GError *e = NULL;
while (TRUE) {
if (!g_file_enumerator_iterate(enumerator, &info, &child, NULL, &e) &&
e) {
g_warning("%s", e->message);
g_clear_error(&e);
continue;
}
if (!info)
break;
while (g_file_enumerator_iterate(enumerator, &info, &child, NULL, NULL) &&
info) {
if (self->filtering && g_file_info_get_is_hidden(info))
continue;
FivIoModelEntry entry = {.uri = g_file_get_uri(child),
.target_uri = g_strdup(g_file_info_get_attribute_string(
info, G_FILE_ATTRIBUTE_STANDARD_TARGET_URI))};
FivIoModelEntry entry = {.uri = g_file_get_uri(child)};
GDateTime *mtime = g_file_info_get_modification_date_time(info);
if (mtime) {
entry.mtime_msec = g_date_time_to_unix(mtime) * 1000 +

View File

@ -130,7 +130,6 @@ GFile *fiv_io_model_get_location(FivIoModel *self);
typedef struct {
gchar *uri; ///< GIO URI
gchar *target_uri; ///< GIO URI for any target
gchar *collate_key; ///< Collate key for the filename
gint64 mtime_msec; ///< Modification time in milliseconds
} FivIoModelEntry;

View File

@ -17,7 +17,6 @@
#include <gtk/gtk.h>
#include "fiv-collection.h"
#include "fiv-context-menu.h"
#include "fiv-io.h"
#include "fiv-sidebar.h"
@ -296,49 +295,16 @@ create_row(FivSidebar *self, GFile *file, const char *icon_name)
return row;
}
static void
on_update_task(GTask *task, G_GNUC_UNUSED gpointer source_object,
G_GNUC_UNUSED gpointer task_data, G_GNUC_UNUSED GCancellable *cancellable)
{
g_task_return_boolean(task, TRUE);
}
static void
on_update_task_done(GObject *source_object, G_GNUC_UNUSED GAsyncResult *res,
G_GNUC_UNUSED gpointer user_data)
{
FivSidebar *self = FIV_SIDEBAR(source_object);
gtk_places_sidebar_set_location(
self->places, fiv_io_model_get_location(self->model));
}
static void
update_location(FivSidebar *self)
{
GFile *location = fiv_io_model_get_location(self->model);
GFile *collection = g_file_new_for_uri(FIV_COLLECTION_SCHEME ":/");
gtk_places_sidebar_remove_shortcut(self->places, collection);
if (location && g_file_has_uri_scheme(location, FIV_COLLECTION_SCHEME)) {
// add_shortcut() asynchronously requests GFileInfo, and only fills in
// the new row's "uri" data field once that's finished, resulting in
// the immediate set_location() call below failing to find it.
gtk_places_sidebar_add_shortcut(self->places, collection);
// Queue up a callback using the same mechanism that GFile uses.
GTask *task = g_task_new(self, NULL, on_update_task_done, NULL);
g_task_set_name(task, __func__);
g_task_set_priority(task, G_PRIORITY_LOW);
g_task_run_in_thread(task, on_update_task);
g_object_unref(task);
}
g_object_unref(collection);
if (!location)
return;
gtk_places_sidebar_set_location(self->places, location);
gtk_container_foreach(GTK_CONTAINER(self->listbox),
(GtkCallback) gtk_widget_destroy, NULL);
if (!location)
return;
GFile *iter = g_object_ref(location);
GtkWidget *row = NULL;

View File

@ -615,7 +615,7 @@ read_png_thumbnail(
}
cairo_surface_t *
fiv_thumbnail_lookup(const char *uri, gint64 mtime_msec, FivThumbnailSize size)
fiv_thumbnail_lookup(char *uri, gint64 mtime_msec, FivThumbnailSize size)
{
g_return_val_if_fail(size >= FIV_THUMBNAIL_SIZE_MIN &&
size <= FIV_THUMBNAIL_SIZE_MAX, NULL);

View File

@ -68,7 +68,7 @@ cairo_surface_t *fiv_thumbnail_produce(
/// Retrieves a thumbnail of the most appropriate quality and resolution
/// for the target file.
cairo_surface_t *fiv_thumbnail_lookup(
const char *uri, gint64 mtime_msec, FivThumbnailSize size);
char *uri, gint64 mtime_msec, FivThumbnailSize size);
/// Invalidate the wide thumbnail cache. May write to standard streams.
void fiv_thumbnail_invalidate(void);

144
fiv.c
View File

@ -36,7 +36,6 @@
#include "config.h"
#include "fiv-browser.h"
#include "fiv-collection.h"
#include "fiv-io.h"
#include "fiv-sidebar.h"
#include "fiv-thumbnail.h"
@ -60,17 +59,6 @@ exit_fatal(const char *format, ...)
exit(EXIT_FAILURE);
}
static gchar **
slist_to_strv(GSList *slist)
{
gchar **strv = g_malloc0_n(g_slist_length(slist) + 1, sizeof *strv),
**p = strv;
for (GSList *link = slist; link; link = link->next)
*p++ = link->data;
g_slist_free(slist);
return strv;
}
// --- Keyboard shortcuts ------------------------------------------------------
// Fuck XML, this can be easily represented in static structures.
// Though it would be nice if the accelerators could be customized.
@ -646,11 +634,7 @@ switch_to_view(void)
static gchar *
parent_uri(GFile *child_file)
{
// The empty URI results in a convenient dummy GFile implementation.
GFile *parent = g_file_get_parent(child_file);
if (!parent)
return g_strdup("");
gchar *parent_uri = g_file_get_uri(parent);
g_object_unref(parent);
return parent_uri;
@ -719,7 +703,7 @@ load_directory_without_switching(const char *uri)
GError *error = NULL;
GFile *file = g_file_new_for_uri(g.directory);
if (fiv_io_model_open(g.model, file, &error)) {
// This is handled by our ::files-changed callback.
// Handled by the signal callback.
} else if (g_error_matches(error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED)) {
g_error_free(error);
} else {
@ -841,7 +825,6 @@ create_open_dialog(void)
"_Cancel", GTK_RESPONSE_CANCEL,
"_Open", GTK_RESPONSE_ACCEPT, NULL);
gtk_file_chooser_set_local_only(GTK_FILE_CHOOSER(dialog), FALSE);
gtk_file_chooser_set_select_multiple(GTK_FILE_CHOOSER(dialog), TRUE);
GtkFileFilter *filter = gtk_file_filter_new();
for (const char **p = fiv_io_supported_media_types; *p; p++)
@ -871,20 +854,13 @@ on_open(void)
(void) gtk_file_chooser_set_current_folder_uri(
GTK_FILE_CHOOSER(dialog), g.directory);
// The default is local-only, single item.
switch (gtk_dialog_run(GTK_DIALOG(dialog))) {
GSList *uri_list;
gchar *uri;
case GTK_RESPONSE_ACCEPT:
if (!(uri_list = gtk_file_chooser_get_uris(GTK_FILE_CHOOSER(dialog))))
break;
gchar **uris = slist_to_strv(uri_list);
if (g_strv_length(uris) == 1) {
open_image(uris[0]);
} else {
fiv_collection_reload(uris);
load_directory(FIV_COLLECTION_SCHEME ":/");
}
g_strfreev(uris);
uri = gtk_file_chooser_get_uri(GTK_FILE_CHOOSER(dialog));
open_image(uri);
g_free(uri);
break;
case GTK_RESPONSE_NONE:
dialog = NULL;
@ -912,61 +888,16 @@ on_next(void)
}
}
static gchar **
build_spawn_argv(const char *uri)
{
// Because we only pass URIs, there is no need to prepend "--" here.
GPtrArray *a = g_ptr_array_new();
g_ptr_array_add(a, g_strdup(PROJECT_NAME));
// Process-local VFS URIs need to be resolved to globally accessible URIs.
// It doesn't seem possible to reliably tell if a GFile is process-local,
// but our collection VFS is the only one to realistically cause problems.
if (!fiv_collection_uri_matches(uri)) {
g_ptr_array_add(a, g_strdup(uri));
goto out;
}
GFile *file = g_file_new_for_uri(uri);
GError *error = NULL;
GFileInfo *info =
g_file_query_info(file, G_FILE_ATTRIBUTE_STANDARD_TARGET_URI,
G_FILE_QUERY_INFO_NONE, NULL, &error);
g_object_unref(file);
if (!info) {
g_warning("%s", error->message);
g_error_free(error);
goto out;
}
const char *target_uri = g_file_info_get_attribute_string(
info, G_FILE_ATTRIBUTE_STANDARD_TARGET_URI);
if (target_uri) {
g_ptr_array_add(a, g_strdup(target_uri));
} else {
gsize len = 0;
GFile **files = fiv_collection_get_contents(&len);
for (gsize i = 0; i < len; i++)
g_ptr_array_add(a, g_file_get_uri(files[i]));
}
g_object_unref(info);
out:
g_ptr_array_add(a, NULL);
return (gchar **) g_ptr_array_free(a, FALSE);
}
static void
spawn_uri(const char *uri)
{
gchar **argv = build_spawn_argv(uri);
char *argv[] = {PROJECT_NAME, (char *) uri, NULL};
GError *error = NULL;
if (!g_spawn_async(
NULL, argv, NULL, G_SPAWN_SEARCH_PATH, NULL, NULL, NULL, &error)) {
g_warning("%s", error->message);
g_error_free(error);
}
g_strfreev(argv);
}
static void
@ -1070,13 +1001,8 @@ on_view_drag_data_received(G_GNUC_UNUSED GtkWidget *widget,
return;
}
GFile *file = NULL;
if (g_strv_length(uris) == 1) {
file = g_file_new_for_uri(uris[0]);
} else {
fiv_collection_reload(uris);
file = g_file_new_for_uri(FIV_COLLECTION_SCHEME ":/");
}
// TODO(p): Once we're able to open a virtual directory, open all of them.
GFile *file = g_file_new_for_uri(uris[0]);
open_any_file(file, FALSE);
g_object_unref(file);
gtk_drag_finish(context, TRUE, FALSE, time);
@ -1924,12 +1850,10 @@ static const char stylesheet[] = "@define-color fiv-tile @content_view_bg; \
.fiv-information label { padding: 0 4px; }";
static void
output_thumbnail(gchar **uris, gboolean extract, const char *size_arg)
output_thumbnail(const char *path_arg, gboolean extract, 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");
if (!path_arg)
exit_fatal("no path given");
FivThumbnailSize size = FIV_THUMBNAIL_SIZE_COUNT;
if (size_arg) {
@ -1947,7 +1871,7 @@ output_thumbnail(gchar **uris, gboolean extract, const char *size_arg)
#endif // G_OS_WIN32
GError *error = NULL;
GFile *file = g_file_new_for_uri(uris[0]);
GFile *file = g_file_new_for_commandline_arg(path_arg);
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);
@ -1970,10 +1894,10 @@ 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;
gchar **path_args = NULL, *thumbnail_size = NULL;
const GOptionEntry options[] = {
{G_OPTION_REMAINING, 0, 0, G_OPTION_ARG_FILENAME_ARRAY, &args,
NULL, "[PATH | URI]..."},
{G_OPTION_REMAINING, 0, 0, G_OPTION_ARG_FILENAME_ARRAY, &path_args,
NULL, "[FILE | DIRECTORY | URI]"},
{"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},
@ -2013,22 +1937,19 @@ main(int argc, char *argv[])
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);
}
// NOTE: Firefox and Eye of GNOME both interpret multiple arguments
// in a special way. This is problematic, because one-element lists
// are unrepresentable.
// TODO(p): Require a command line switch, load a virtual folder.
// We may want or need to create a custom GVfs.
// TODO(p): Complain to the user if there's more than one argument.
// Best show the help message, if we can figure that out.
const gchar *path_arg = path_args ? path_args[0] : NULL;
if (extract_thumbnail || thumbnail_size) {
output_thumbnail(args, extract_thumbnail, thumbnail_size);
output_thumbnail(path_arg, extract_thumbnail, thumbnail_size);
return 0;
}
// It doesn't make much sense to have command line arguments able to
// resolve to the VFS they may end up being contained within.
fiv_collection_register();
g.model = g_object_new(FIV_TYPE_IO_MODEL, NULL);
g_signal_connect(g.model, "files-changed",
G_CALLBACK(on_model_files_changed), NULL);
@ -2163,22 +2084,11 @@ main(int argc, char *argv[])
// XXX: The widget wants to read the display's profile. The realize is ugly.
gtk_widget_realize(g.view);
// XXX: We follow the behaviour of Firefox and Eye of GNOME, which both
// interpret multiple command line arguments differently, as a collection.
// However, single-element collections are unrepresentable this way.
// Should we allow multiple targets only in a special new mode?
g.files = g_ptr_array_new_full(0, g_free);
if (args) {
const gchar *target = *args;
if (args[1]) {
fiv_collection_reload(args);
target = FIV_COLLECTION_SCHEME ":/";
}
GFile *file = g_file_new_for_uri(target);
if (path_arg) {
GFile *file = g_file_new_for_commandline_arg(path_arg);
open_any_file(file, browse);
g_object_unref(file);
g_strfreev(args);
}
if (!g.directory) {
GFile *file = g_file_new_for_path(".");

View File

@ -4,7 +4,7 @@ Name=fiv
GenericName=Image Viewer
X-GNOME-FullName=fiv Image Viewer
Icon=fiv
Exec=fiv -- %U
Exec=fiv -- %u
Terminal=false
StartupNotify=true
Categories=Graphics;2DGraphics;Viewer;

View File

@ -107,10 +107,9 @@ tiff_tables = custom_target('tiff-tables.h',
desktops = ['fiv.desktop', 'fiv-browse.desktop']
exe = executable('fiv', 'fiv.c', 'fiv-view.c', 'fiv-io.c', 'fiv-context-menu.c',
'fiv-browser.c', 'fiv-sidebar.c', 'fiv-thumbnail.c', 'fiv-collection.c',
'xdg.c', resources,
'fiv-browser.c', 'fiv-sidebar.c', 'fiv-thumbnail.c', 'xdg.c', resources,
install : true,
dependencies : dependencies)
dependencies : [dependencies])
if gdkpixbuf.found()
executable('io-benchmark', 'fiv-io-benchmark.c', 'fiv-io.c', 'xdg.c',
build_by_default : false,

View File

@ -11,6 +11,5 @@
<file preprocess="xml-stripblanks">heal-symbolic.svg</file>
<file preprocess="xml-stripblanks">info-symbolic.svg</file>
<file preprocess="xml-stripblanks">pin2-symbolic.svg</file>
<file preprocess="xml-stripblanks">shapes-symbolic.svg</file>
</gresource>
</gresources>

View File

@ -1,154 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg height="16px" viewBox="0 0 16 16" width="16px" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<filter id="a" height="100%" width="100%" x="0%" y="0%">
<feColorMatrix in="SourceGraphic" type="matrix" values="0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 0 0 0 1 0"/>
</filter>
<mask id="b">
<g filter="url(#a)">
<path d="m 0 0 h 16 v 16 h -16 z" fill-opacity="0.3"/>
</g>
</mask>
<clipPath id="c">
<path d="m 0 0 h 1024 v 800 h -1024 z"/>
</clipPath>
<mask id="d">
<g filter="url(#a)">
<path d="m 0 0 h 16 v 16 h -16 z" fill-opacity="0.05"/>
</g>
</mask>
<clipPath id="e">
<path d="m 0 0 h 1024 v 800 h -1024 z"/>
</clipPath>
<mask id="f">
<g filter="url(#a)">
<path d="m 0 0 h 16 v 16 h -16 z" fill-opacity="0.05"/>
</g>
</mask>
<clipPath id="g">
<path d="m 0 0 h 1024 v 800 h -1024 z"/>
</clipPath>
<mask id="h">
<g filter="url(#a)">
<path d="m 0 0 h 16 v 16 h -16 z" fill-opacity="0.05"/>
</g>
</mask>
<clipPath id="i">
<path d="m 0 0 h 1024 v 800 h -1024 z"/>
</clipPath>
<mask id="j">
<g filter="url(#a)">
<path d="m 0 0 h 16 v 16 h -16 z" fill-opacity="0.05"/>
</g>
</mask>
<clipPath id="k">
<path d="m 0 0 h 1024 v 800 h -1024 z"/>
</clipPath>
<mask id="l">
<g filter="url(#a)">
<path d="m 0 0 h 16 v 16 h -16 z" fill-opacity="0.05"/>
</g>
</mask>
<clipPath id="m">
<path d="m 0 0 h 1024 v 800 h -1024 z"/>
</clipPath>
<mask id="n">
<g filter="url(#a)">
<path d="m 0 0 h 16 v 16 h -16 z" fill-opacity="0.05"/>
</g>
</mask>
<clipPath id="o">
<path d="m 0 0 h 1024 v 800 h -1024 z"/>
</clipPath>
<mask id="p">
<g filter="url(#a)">
<path d="m 0 0 h 16 v 16 h -16 z" fill-opacity="0.3"/>
</g>
</mask>
<clipPath id="q">
<path d="m 0 0 h 1024 v 800 h -1024 z"/>
</clipPath>
<mask id="r">
<g filter="url(#a)">
<path d="m 0 0 h 16 v 16 h -16 z" fill-opacity="0.5"/>
</g>
</mask>
<clipPath id="s">
<path d="m 0 0 h 1024 v 800 h -1024 z"/>
</clipPath>
<mask id="t">
<g filter="url(#a)">
<path d="m 0 0 h 16 v 16 h -16 z" fill-opacity="0.4"/>
</g>
</mask>
<clipPath id="u">
<path d="m 0 0 h 1024 v 800 h -1024 z"/>
</clipPath>
<mask id="v">
<g filter="url(#a)">
<path d="m 0 0 h 16 v 16 h -16 z" fill-opacity="0.4"/>
</g>
</mask>
<clipPath id="w">
<path d="m 0 0 h 1024 v 800 h -1024 z"/>
</clipPath>
<mask id="x">
<g filter="url(#a)">
<path d="m 0 0 h 16 v 16 h -16 z" fill-opacity="0.5"/>
</g>
</mask>
<clipPath id="y">
<path d="m 0 0 h 1024 v 800 h -1024 z"/>
</clipPath>
<mask id="z">
<g filter="url(#a)">
<path d="m 0 0 h 16 v 16 h -16 z" fill-opacity="0.5"/>
</g>
</mask>
<clipPath id="A">
<path d="m 0 0 h 1024 v 800 h -1024 z"/>
</clipPath>
<g fill="#2e3436">
<path d="m 5.191406 1.296875 c -0.390625 -0.390625 -1.023437 -0.390625 -1.414062 0 l -2.5 2.5 c -0.390625 0.390625 -0.390625 1.023437 0 1.414063 l 2.5 2.5 c 0.390625 0.390624 1.023437 0.390624 1.414062 0 l 2.496094 -2.5 c 0.390625 -0.390626 0.390625 -1.023438 0 -1.414063 z m 0 0"/>
<path d="m 9.984375 12.003906 c 0 1.65625 -1.34375 3 -3 3 c -1.660156 0 -3 -1.34375 -3 -3 s 1.339844 -3 3 -3 c 1.65625 0 3 1.34375 3 3 z m 0 0"/>
<path d="m 11.929688 2.007812 c -0.339844 0.015626 -0.644532 0.203126 -0.8125 0.496094 l -2.320313 4 c -0.386719 0.664063 0.09375 1.5 0.863281 1.5 h 4.644532 c 0.769531 0 1.25 -0.835937 0.863281 -1.5 l -2.320313 -4 c -0.1875 -0.328125 -0.542968 -0.519531 -0.917968 -0.496094 z m 0 0"/>
</g>
<g clip-path="url(#c)" mask="url(#b)" transform="matrix(1 0 0 1 -620 -420)">
<path d="m 562.460938 212.058594 h 10.449218 c -1.183594 0.492187 -1.296875 2.460937 0 3 h -10.449218 z m 0 0" fill="#2e3436"/>
</g>
<g clip-path="url(#e)" mask="url(#d)" transform="matrix(1 0 0 1 -620 -420)">
<path d="m 16 632 h 1 v 1 h -1 z m 0 0" fill="#2e3436" fill-rule="evenodd"/>
</g>
<g clip-path="url(#g)" mask="url(#f)" transform="matrix(1 0 0 1 -620 -420)">
<path d="m 17 631 h 1 v 1 h -1 z m 0 0" fill="#2e3436" fill-rule="evenodd"/>
</g>
<g clip-path="url(#i)" mask="url(#h)" transform="matrix(1 0 0 1 -620 -420)">
<path d="m 18 634 h 1 v 1 h -1 z m 0 0" fill="#2e3436" fill-rule="evenodd"/>
</g>
<g clip-path="url(#k)" mask="url(#j)" transform="matrix(1 0 0 1 -620 -420)">
<path d="m 16 634 h 1 v 1 h -1 z m 0 0" fill="#2e3436" fill-rule="evenodd"/>
</g>
<g clip-path="url(#m)" mask="url(#l)" transform="matrix(1 0 0 1 -620 -420)">
<path d="m 17 635 h 1 v 1 h -1 z m 0 0" fill="#2e3436" fill-rule="evenodd"/>
</g>
<g clip-path="url(#o)" mask="url(#n)" transform="matrix(1 0 0 1 -620 -420)">
<path d="m 19 635 h 1 v 1 h -1 z m 0 0" fill="#2e3436" fill-rule="evenodd"/>
</g>
<g clip-path="url(#q)" mask="url(#p)" transform="matrix(1 0 0 1 -620 -420)">
<path d="m 136 660 v 7 h 7 v -7 z m 0 0" fill="#2e3436"/>
</g>
<g clip-path="url(#s)" mask="url(#r)" transform="matrix(1 0 0 1 -620 -420)">
<path d="m 199 642 h 3 v 12 h -3 z m 0 0" fill="#2e3436"/>
</g>
<g clip-path="url(#u)" mask="url(#t)" transform="matrix(1 0 0 1 -620 -420)">
<path d="m 209.5 144.160156 c 0.277344 0 0.5 0.222656 0.5 0.5 v 1 c 0 0.277344 -0.222656 0.5 -0.5 0.5 s -0.5 -0.222656 -0.5 -0.5 v -1 c 0 -0.277344 0.222656 -0.5 0.5 -0.5 z m 0 0" fill="#2e3436"/>
</g>
<g clip-path="url(#w)" mask="url(#v)" transform="matrix(1 0 0 1 -620 -420)">
<path d="m 206.5 144.160156 c 0.277344 0 0.5 0.222656 0.5 0.5 v 1 c 0 0.277344 -0.222656 0.5 -0.5 0.5 s -0.5 -0.222656 -0.5 -0.5 v -1 c 0 -0.277344 0.222656 -0.5 0.5 -0.5 z m 0 0" fill="#2e3436"/>
</g>
<g clip-path="url(#y)" mask="url(#x)" transform="matrix(1 0 0 1 -620 -420)">
<path d="m 229.5 143.160156 c -0.546875 0 -1 0.457032 -1 1 c 0 0.546875 0.453125 1 1 1 s 1 -0.453125 1 -1 c 0 -0.542968 -0.453125 -1 -1 -1 z m 0 0" fill="#2e3436"/>
</g>
<g clip-path="url(#A)" mask="url(#z)" transform="matrix(1 0 0 1 -620 -420)">
<path d="m 226.453125 143.160156 c -0.519531 0 -0.953125 0.433594 -0.953125 0.953125 v 0.09375 c 0 0.519531 0.433594 0.953125 0.953125 0.953125 h 0.09375 c 0.519531 0 0.953125 -0.433594 0.953125 -0.953125 v -0.09375 c 0 -0.519531 -0.433594 -0.953125 -0.953125 -0.953125 z m 0 0" fill="#2e3436"/>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 6.7 KiB