Convert all loading to use GFile
Now we're able to make use of GVfs, with some caveats.
This commit is contained in:
parent
8b232dc444
commit
380ddd540b
|
@ -3,7 +3,7 @@ Type=Application
|
||||||
Name=fastiv
|
Name=fastiv
|
||||||
GenericName=File Browser
|
GenericName=File Browser
|
||||||
Icon=fastiv
|
Icon=fastiv
|
||||||
Exec=fastiv --browse -- %f
|
Exec=fastiv --browse -- %u
|
||||||
NoDisplay=true
|
NoDisplay=true
|
||||||
Terminal=false
|
Terminal=false
|
||||||
StartupNotify=true
|
StartupNotify=true
|
||||||
|
|
236
fastiv.c
236
fastiv.c
|
@ -263,13 +263,12 @@ struct {
|
||||||
gchar **supported_globs;
|
gchar **supported_globs;
|
||||||
gboolean filtering;
|
gboolean filtering;
|
||||||
|
|
||||||
gchar *directory; ///< Full path to the currently browsed directory
|
gchar *uri; ///< Current image URI
|
||||||
|
gchar *directory; ///< URI of the currently browsed directory
|
||||||
GList *directory_back; ///< History paths going backwards
|
GList *directory_back; ///< History paths going backwards
|
||||||
GList *directory_forward; ///< History paths going forwards
|
GList *directory_forward; ///< History paths going forwards
|
||||||
GPtrArray *files;
|
GPtrArray *files; ///< "directory" contents
|
||||||
gint files_index;
|
gint files_index; ///< Where "uri" is within "files"
|
||||||
|
|
||||||
gchar *path;
|
|
||||||
|
|
||||||
GtkWidget *window;
|
GtkWidget *window;
|
||||||
GtkWidget *stack;
|
GtkWidget *stack;
|
||||||
|
@ -322,15 +321,25 @@ show_error_dialog(GError *error)
|
||||||
static void
|
static void
|
||||||
switch_to_browser(void)
|
switch_to_browser(void)
|
||||||
{
|
{
|
||||||
gtk_window_set_title(GTK_WINDOW(g.window), g.directory);
|
// TODO(p): Use g_file_get_parse_name() or something.
|
||||||
|
GFile *file = g_file_new_for_uri(g.directory);
|
||||||
|
const char *path = g_file_peek_path(file);
|
||||||
|
gtk_window_set_title(GTK_WINDOW(g.window), path ? path : g.directory);
|
||||||
|
g_object_unref(file);
|
||||||
|
|
||||||
gtk_stack_set_visible_child(GTK_STACK(g.stack), g.browser_paned);
|
gtk_stack_set_visible_child(GTK_STACK(g.stack), g.browser_paned);
|
||||||
gtk_widget_grab_focus(g.browser_scroller);
|
gtk_widget_grab_focus(g.browser_scroller);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
switch_to_view(const char *path)
|
switch_to_view(const char *uri)
|
||||||
{
|
{
|
||||||
gtk_window_set_title(GTK_WINDOW(g.window), path);
|
// TODO(p): Use g_file_get_parse_name() or something.
|
||||||
|
GFile *file = g_file_new_for_uri(uri);
|
||||||
|
const char *path = g_file_peek_path(file);
|
||||||
|
gtk_window_set_title(GTK_WINDOW(g.window), path ? path : uri);
|
||||||
|
g_object_unref(file);
|
||||||
|
|
||||||
gtk_stack_set_visible_child(GTK_STACK(g.stack), g.view_box);
|
gtk_stack_set_visible_child(GTK_STACK(g.stack), g.view_box);
|
||||||
gtk_widget_grab_focus(g.view);
|
gtk_widget_grab_focus(g.view);
|
||||||
}
|
}
|
||||||
|
@ -338,38 +347,37 @@ switch_to_view(const char *path)
|
||||||
static gint
|
static gint
|
||||||
files_compare(gconstpointer a, gconstpointer b)
|
files_compare(gconstpointer a, gconstpointer b)
|
||||||
{
|
{
|
||||||
gchar *path1 = g_canonicalize_filename(*(gchar **) a, g.directory);
|
GFile *file1 = g_file_new_for_uri(*(gchar **) a);
|
||||||
gchar *path2 = g_canonicalize_filename(*(gchar **) b, g.directory);
|
GFile *file2 = g_file_new_for_uri(*(gchar **) b);
|
||||||
GFile *location1 = g_file_new_for_path(path1);
|
gint result = fiv_io_filecmp(file1, file2);
|
||||||
GFile *location2 = g_file_new_for_path(path2);
|
g_object_unref(file1);
|
||||||
g_free(path1);
|
g_object_unref(file2);
|
||||||
g_free(path2);
|
|
||||||
gint result = fiv_io_filecmp(location1, location2);
|
|
||||||
g_object_unref(location1);
|
|
||||||
g_object_unref(location2);
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static gchar *
|
||||||
|
parent_uri(GFile *child_file)
|
||||||
|
{
|
||||||
|
GFile *parent = g_file_get_parent(child_file);
|
||||||
|
gchar *parent_uri = g_file_get_uri(parent);
|
||||||
|
g_object_unref(parent);
|
||||||
|
return parent_uri;
|
||||||
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
update_files_index(void)
|
update_files_index(void)
|
||||||
{
|
{
|
||||||
g.files_index = -1;
|
g.files_index = -1;
|
||||||
|
for (guint i = 0; i < g.files->len; i++)
|
||||||
// FIXME: We presume that this basename is from the same directory.
|
if (!g_strcmp0(g.uri, g_ptr_array_index(g.files, i)))
|
||||||
gchar *basename = g.path ? g_path_get_basename(g.path) : NULL;
|
|
||||||
for (guint i = 0; i < g.files->len; i++) {
|
|
||||||
if (!g_strcmp0(basename, g_ptr_array_index(g.files, i)))
|
|
||||||
g.files_index = i;
|
g.files_index = i;
|
||||||
}
|
|
||||||
g_free(basename);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
load_directory_without_reload(const gchar *dirname)
|
load_directory_without_reload(const gchar *uri)
|
||||||
{
|
{
|
||||||
gchar *dirname_duplicated = g_strdup(dirname);
|
gchar *uri_duplicated = g_strdup(uri);
|
||||||
if (g.directory_back &&
|
if (g.directory_back && !strcmp(uri, g.directory_back->data)) {
|
||||||
!strcmp(dirname, g.directory_back->data)) {
|
|
||||||
// We're going back in history.
|
// We're going back in history.
|
||||||
if (g.directory) {
|
if (g.directory) {
|
||||||
g.directory_forward =
|
g.directory_forward =
|
||||||
|
@ -380,8 +388,7 @@ load_directory_without_reload(const gchar *dirname)
|
||||||
GList *link = g.directory_back;
|
GList *link = g.directory_back;
|
||||||
g.directory_back = g_list_remove_link(g.directory_back, link);
|
g.directory_back = g_list_remove_link(g.directory_back, link);
|
||||||
g_list_free_full(link, g_free);
|
g_list_free_full(link, g_free);
|
||||||
} else if (g.directory_forward &&
|
} else if (g.directory_forward && !strcmp(uri, g.directory_forward->data)) {
|
||||||
!strcmp(dirname, g.directory_forward->data)) {
|
|
||||||
// We're going forward in history.
|
// We're going forward in history.
|
||||||
if (g.directory) {
|
if (g.directory) {
|
||||||
g.directory_back =
|
g.directory_back =
|
||||||
|
@ -392,7 +399,7 @@ load_directory_without_reload(const gchar *dirname)
|
||||||
GList *link = g.directory_forward;
|
GList *link = g.directory_forward;
|
||||||
g.directory_forward = g_list_remove_link(g.directory_forward, link);
|
g.directory_forward = g_list_remove_link(g.directory_forward, link);
|
||||||
g_list_free_full(link, g_free);
|
g_list_free_full(link, g_free);
|
||||||
} else if (g.directory && strcmp(dirname, g.directory)) {
|
} else if (g.directory && strcmp(uri, g.directory)) {
|
||||||
// We're on a new subpath.
|
// We're on a new subpath.
|
||||||
g_list_free_full(g.directory_forward, g_free);
|
g_list_free_full(g.directory_forward, g_free);
|
||||||
g.directory_forward = NULL;
|
g.directory_forward = NULL;
|
||||||
|
@ -402,14 +409,14 @@ load_directory_without_reload(const gchar *dirname)
|
||||||
}
|
}
|
||||||
|
|
||||||
g_free(g.directory);
|
g_free(g.directory);
|
||||||
g.directory = dirname_duplicated;
|
g.directory = uri_duplicated;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
load_directory(const gchar *dirname)
|
load_directory(const gchar *uri)
|
||||||
{
|
{
|
||||||
if (dirname) {
|
if (uri) {
|
||||||
load_directory_without_reload(dirname);
|
load_directory_without_reload(uri);
|
||||||
|
|
||||||
GtkAdjustment *vadjustment = gtk_scrolled_window_get_vadjustment(
|
GtkAdjustment *vadjustment = gtk_scrolled_window_get_vadjustment(
|
||||||
GTK_SCROLLED_WINDOW(g.browser_scroller));
|
GTK_SCROLLED_WINDOW(g.browser_scroller));
|
||||||
|
@ -420,30 +427,35 @@ load_directory(const gchar *dirname)
|
||||||
g_ptr_array_set_size(g.files, 0);
|
g_ptr_array_set_size(g.files, 0);
|
||||||
g.files_index = -1;
|
g.files_index = -1;
|
||||||
|
|
||||||
GFile *file = g_file_new_for_path(g.directory);
|
GFile *file = g_file_new_for_uri(g.directory);
|
||||||
fiv_sidebar_set_location(FIV_SIDEBAR(g.browser_sidebar), file);
|
fiv_sidebar_set_location(FIV_SIDEBAR(g.browser_sidebar), file);
|
||||||
g_object_unref(file);
|
|
||||||
fiv_browser_load(
|
fiv_browser_load(
|
||||||
FIV_BROWSER(g.browser), g.filtering ? is_supported : NULL, g.directory);
|
FIV_BROWSER(g.browser), g.filtering ? is_supported : NULL, g.directory);
|
||||||
|
|
||||||
GError *error = NULL;
|
GError *error = NULL;
|
||||||
GDir *dir = g_dir_open(g.directory, 0, &error);
|
GFileEnumerator *enumerator = g_file_enumerate_children(file,
|
||||||
if (dir) {
|
G_FILE_ATTRIBUTE_STANDARD_NAME "," G_FILE_ATTRIBUTE_STANDARD_TYPE,
|
||||||
for (const gchar *name = NULL; (name = g_dir_read_name(dir)); ) {
|
G_FILE_QUERY_INFO_NONE, NULL, &error);
|
||||||
// This really wants to make you use readdir() directly.
|
if (enumerator) {
|
||||||
char *absolute = g_canonicalize_filename(name, g.directory);
|
GFileInfo *info = NULL;
|
||||||
gboolean is_dir = g_file_test(absolute, G_FILE_TEST_IS_DIR);
|
GFile *child = NULL;
|
||||||
g_free(absolute);
|
while (
|
||||||
if (!is_dir && is_supported(name))
|
g_file_enumerator_iterate(enumerator, &info, &child, NULL, NULL) &&
|
||||||
g_ptr_array_add(g.files, g_strdup(name));
|
info) {
|
||||||
|
// TODO(p): What encoding does g_file_info_get_name() return?
|
||||||
|
if (g_file_info_get_file_type(info) != G_FILE_TYPE_DIRECTORY &&
|
||||||
|
is_supported(g_file_info_get_name(info)))
|
||||||
|
g_ptr_array_add(g.files, g_file_get_uri(child));
|
||||||
}
|
}
|
||||||
g_dir_close(dir);
|
g_object_unref(enumerator);
|
||||||
|
|
||||||
g_ptr_array_sort(g.files, files_compare);
|
g_ptr_array_sort(g.files, files_compare);
|
||||||
update_files_index();
|
update_files_index();
|
||||||
} else {
|
} else {
|
||||||
|
// TODO(p): Handle "Operation not supported" silently.
|
||||||
show_error_dialog(error);
|
show_error_dialog(error);
|
||||||
}
|
}
|
||||||
|
g_object_unref(file);
|
||||||
|
|
||||||
gtk_widget_set_sensitive(
|
gtk_widget_set_sensitive(
|
||||||
g.toolbar[TOOLBAR_FILE_PREVIOUS], g.files->len > 1);
|
g.toolbar[TOOLBAR_FILE_PREVIOUS], g.files->len > 1);
|
||||||
|
@ -454,7 +466,7 @@ load_directory(const gchar *dirname)
|
||||||
// XXX: When something outside the filtered entries is open, the index is
|
// XXX: When something outside the filtered entries is open, the index is
|
||||||
// kept at -1, and browsing doesn't work. How to behave here?
|
// kept at -1, and browsing doesn't work. How to behave here?
|
||||||
// Should we add it to the pointer array as an exception?
|
// Should we add it to the pointer array as an exception?
|
||||||
if (dirname)
|
if (uri)
|
||||||
switch_to_browser();
|
switch_to_browser();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -467,41 +479,40 @@ on_filtering_toggled(GtkToggleButton *button, G_GNUC_UNUSED gpointer user_data)
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
open(const gchar *path)
|
open(const gchar *uri)
|
||||||
{
|
{
|
||||||
g_return_if_fail(g_path_is_absolute(path));
|
GFile *file = g_file_new_for_uri(uri);
|
||||||
|
|
||||||
GError *error = NULL;
|
GError *error = NULL;
|
||||||
if (!fiv_view_open(FIV_VIEW(g.view), path, &error)) {
|
if (!fiv_view_open(FIV_VIEW(g.view), uri, &error)) {
|
||||||
char *base = g_filename_display_basename(path);
|
const char *path = g_file_peek_path(file);
|
||||||
|
// TODO(p): Use g_file_info_get_display_name().
|
||||||
|
gchar *base = g_filename_display_basename(path ? path : uri);
|
||||||
|
g_object_unref(file);
|
||||||
|
|
||||||
g_prefix_error(&error, "%s: ", base);
|
g_prefix_error(&error, "%s: ", base);
|
||||||
show_error_dialog(error);
|
show_error_dialog(error);
|
||||||
g_free(base);
|
g_free(base);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
gchar *uri = g_filename_to_uri(path, NULL, NULL);
|
|
||||||
if (uri) {
|
|
||||||
gtk_recent_manager_add_item(gtk_recent_manager_get_default(), uri);
|
gtk_recent_manager_add_item(gtk_recent_manager_get_default(), uri);
|
||||||
g_free(uri);
|
|
||||||
}
|
|
||||||
|
|
||||||
g_list_free_full(g.directory_forward, g_free);
|
g_list_free_full(g.directory_forward, g_free);
|
||||||
g.directory_forward = NULL;
|
g.directory_forward = NULL;
|
||||||
|
|
||||||
g_free(g.path);
|
g_free(g.uri);
|
||||||
g.path = g_strdup(path);
|
g.uri = g_strdup(uri);
|
||||||
|
|
||||||
// So that load_directory() itself can be used for reloading.
|
// So that load_directory() itself can be used for reloading.
|
||||||
gchar *dirname = g_path_get_dirname(path);
|
gchar *parent = parent_uri(file);
|
||||||
if (!g.files->len /* hack to always load the directory after launch */ ||
|
if (!g.files->len /* hack to always load the directory after launch */ ||
|
||||||
!g.directory || strcmp(dirname, g.directory))
|
!g.directory || strcmp(parent, g.directory))
|
||||||
load_directory(dirname);
|
load_directory(parent);
|
||||||
else
|
else
|
||||||
update_files_index();
|
update_files_index();
|
||||||
g_free(dirname);
|
g_free(parent);
|
||||||
|
|
||||||
switch_to_view(path);
|
switch_to_view(uri);
|
||||||
}
|
}
|
||||||
|
|
||||||
static GtkWidget *
|
static GtkWidget *
|
||||||
|
@ -511,6 +522,7 @@ create_open_dialog(void)
|
||||||
GTK_WINDOW(g.window), GTK_FILE_CHOOSER_ACTION_OPEN,
|
GTK_WINDOW(g.window), GTK_FILE_CHOOSER_ACTION_OPEN,
|
||||||
"_Cancel", GTK_RESPONSE_CANCEL,
|
"_Cancel", GTK_RESPONSE_CANCEL,
|
||||||
"_Open", GTK_RESPONSE_ACCEPT, NULL);
|
"_Open", GTK_RESPONSE_ACCEPT, NULL);
|
||||||
|
gtk_file_chooser_set_local_only(GTK_FILE_CHOOSER(dialog), FALSE);
|
||||||
|
|
||||||
GtkFileFilter *filter = gtk_file_filter_new();
|
GtkFileFilter *filter = gtk_file_filter_new();
|
||||||
for (const char **p = fiv_io_supported_media_types; *p; p++)
|
for (const char **p = fiv_io_supported_media_types; *p; p++)
|
||||||
|
@ -539,13 +551,13 @@ on_open(void)
|
||||||
// that it will remember its last location.
|
// that it will remember its last location.
|
||||||
gtk_file_chooser_set_current_folder(GTK_FILE_CHOOSER(dialog), g.directory);
|
gtk_file_chooser_set_current_folder(GTK_FILE_CHOOSER(dialog), g.directory);
|
||||||
|
|
||||||
// The default is local-only, single item. Paths are returned absolute.
|
// The default is local-only, single item.
|
||||||
switch (gtk_dialog_run(GTK_DIALOG(dialog))) {
|
switch (gtk_dialog_run(GTK_DIALOG(dialog))) {
|
||||||
gchar *path;
|
gchar *uri;
|
||||||
case GTK_RESPONSE_ACCEPT:
|
case GTK_RESPONSE_ACCEPT:
|
||||||
path = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(dialog));
|
uri = gtk_file_chooser_get_uri(GTK_FILE_CHOOSER(dialog));
|
||||||
open(path);
|
open(uri);
|
||||||
g_free(path);
|
g_free(uri);
|
||||||
break;
|
break;
|
||||||
case GTK_RESPONSE_NONE:
|
case GTK_RESPONSE_NONE:
|
||||||
dialog = NULL;
|
dialog = NULL;
|
||||||
|
@ -561,10 +573,7 @@ on_previous(void)
|
||||||
if (g.files_index >= 0) {
|
if (g.files_index >= 0) {
|
||||||
int previous =
|
int previous =
|
||||||
(g.files->len - 1 + g.files_index - 1) % (g.files->len - 1);
|
(g.files->len - 1 + g.files_index - 1) % (g.files->len - 1);
|
||||||
char *absolute = g_canonicalize_filename(
|
open(g_ptr_array_index(g.files, previous));
|
||||||
g_ptr_array_index(g.files, previous), g.directory);
|
|
||||||
open(absolute);
|
|
||||||
g_free(absolute);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -573,17 +582,14 @@ on_next(void)
|
||||||
{
|
{
|
||||||
if (g.files_index >= 0) {
|
if (g.files_index >= 0) {
|
||||||
int next = (g.files_index + 1) % (g.files->len - 1);
|
int next = (g.files_index + 1) % (g.files->len - 1);
|
||||||
char *absolute = g_canonicalize_filename(
|
open(g_ptr_array_index(g.files, next));
|
||||||
g_ptr_array_index(g.files, next), g.directory);
|
|
||||||
open(absolute);
|
|
||||||
g_free(absolute);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
spawn_path(const char *path)
|
spawn_uri(const char *uri)
|
||||||
{
|
{
|
||||||
char *argv[] = {PROJECT_NAME, (char *) path, NULL};
|
char *argv[] = {PROJECT_NAME, (char *) uri, NULL};
|
||||||
GError *error = NULL;
|
GError *error = NULL;
|
||||||
g_spawn_async(
|
g_spawn_async(
|
||||||
NULL, argv, NULL, G_SPAWN_SEARCH_PATH, NULL, NULL, NULL, &error);
|
NULL, argv, NULL, G_SPAWN_SEARCH_PATH, NULL, NULL, NULL, &error);
|
||||||
|
@ -594,37 +600,35 @@ static void
|
||||||
on_item_activated(G_GNUC_UNUSED FivBrowser *browser, GFile *location,
|
on_item_activated(G_GNUC_UNUSED FivBrowser *browser, GFile *location,
|
||||||
GtkPlacesOpenFlags flags, G_GNUC_UNUSED gpointer data)
|
GtkPlacesOpenFlags flags, G_GNUC_UNUSED gpointer data)
|
||||||
{
|
{
|
||||||
gchar *path = g_file_get_path(location);
|
gchar *uri = g_file_get_uri(location);
|
||||||
if (path) {
|
|
||||||
if (flags == GTK_PLACES_OPEN_NEW_WINDOW)
|
if (flags == GTK_PLACES_OPEN_NEW_WINDOW)
|
||||||
spawn_path(path);
|
spawn_uri(uri);
|
||||||
else
|
else
|
||||||
open(path);
|
open(uri);
|
||||||
g_free(path);
|
g_free(uri);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static gboolean
|
static gboolean
|
||||||
open_any_path(const char *path, gboolean force_browser)
|
open_any_uri(const char *uri, gboolean force_browser)
|
||||||
{
|
{
|
||||||
GStatBuf st;
|
GFile *file = g_file_new_for_uri(uri);
|
||||||
gchar *canonical = g_canonicalize_filename(path, g.directory);
|
GFileType type = g_file_query_file_type(file, G_FILE_QUERY_INFO_NONE, NULL);
|
||||||
gboolean success = !g_stat(canonical, &st);
|
gboolean success = type != G_FILE_TYPE_UNKNOWN;
|
||||||
if (!success) {
|
if (!success) {
|
||||||
show_error_dialog(g_error_new(G_FILE_ERROR,
|
show_error_dialog(g_error_new(G_FILE_ERROR,
|
||||||
g_file_error_from_errno(errno), "%s: %s", path, g_strerror(errno)));
|
g_file_error_from_errno(errno), "%s: %s", uri, g_strerror(ENOENT)));
|
||||||
} else if (S_ISDIR(st.st_mode)) {
|
} else if (type == G_FILE_TYPE_DIRECTORY) {
|
||||||
load_directory(canonical);
|
load_directory(uri);
|
||||||
} else if (force_browser) {
|
} else if (force_browser) {
|
||||||
// GNOME, e.g., invokes this as a hint to focus the particular file,
|
// GNOME, e.g., invokes this as a hint to focus the particular file,
|
||||||
// which we can't currently do yet.
|
// which we can't currently do yet.
|
||||||
gchar *directory = g_path_get_dirname(canonical);
|
gchar *parent = parent_uri(file);
|
||||||
load_directory(directory);
|
load_directory(parent);
|
||||||
g_free(directory);
|
g_free(parent);
|
||||||
} else {
|
} else {
|
||||||
open(canonical);
|
open(uri);
|
||||||
}
|
}
|
||||||
g_free(canonical);
|
g_object_unref(file);
|
||||||
return success;
|
return success;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -632,14 +636,12 @@ static void
|
||||||
on_open_location(G_GNUC_UNUSED GtkPlacesSidebar *sidebar, GFile *location,
|
on_open_location(G_GNUC_UNUSED GtkPlacesSidebar *sidebar, GFile *location,
|
||||||
GtkPlacesOpenFlags flags, G_GNUC_UNUSED gpointer user_data)
|
GtkPlacesOpenFlags flags, G_GNUC_UNUSED gpointer user_data)
|
||||||
{
|
{
|
||||||
gchar *path = g_file_get_path(location);
|
gchar *uri = g_file_get_uri(location);
|
||||||
if (path) {
|
|
||||||
if (flags & GTK_PLACES_OPEN_NEW_WINDOW)
|
if (flags & GTK_PLACES_OPEN_NEW_WINDOW)
|
||||||
spawn_path(path);
|
spawn_uri(uri);
|
||||||
else
|
else
|
||||||
open_any_path(path, FALSE);
|
open_any_uri(uri, FALSE);
|
||||||
g_free(path);
|
g_free(uri);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
|
@ -750,7 +752,7 @@ on_key_press(G_GNUC_UNUSED GtkWidget *widget, GdkEventKey *event,
|
||||||
fiv_sidebar_show_enter_location(FIV_SIDEBAR(g.browser_sidebar));
|
fiv_sidebar_show_enter_location(FIV_SIDEBAR(g.browser_sidebar));
|
||||||
return TRUE;
|
return TRUE;
|
||||||
case GDK_KEY_n:
|
case GDK_KEY_n:
|
||||||
spawn_path(g.directory);
|
spawn_uri(g.directory);
|
||||||
return TRUE;
|
return TRUE;
|
||||||
case GDK_KEY_r:
|
case GDK_KEY_r:
|
||||||
// TODO(p): Reload the image instead, if it's currently visible.
|
// TODO(p): Reload the image instead, if it's currently visible.
|
||||||
|
@ -778,12 +780,13 @@ on_key_press(G_GNUC_UNUSED GtkWidget *widget, GdkEventKey *event,
|
||||||
if (g.directory_forward)
|
if (g.directory_forward)
|
||||||
load_directory(g.directory_forward->data);
|
load_directory(g.directory_forward->data);
|
||||||
else
|
else
|
||||||
switch_to_view(g.path);
|
switch_to_view(g.uri);
|
||||||
return TRUE;
|
return TRUE;
|
||||||
case GDK_KEY_Up:
|
case GDK_KEY_Up:
|
||||||
if (gtk_stack_get_visible_child(GTK_STACK(g.stack)) != g.view_box) {
|
if (gtk_stack_get_visible_child(GTK_STACK(g.stack)) != g.view_box) {
|
||||||
// This isn't exact, trailing slashes should be ignored.
|
GFile *directory = g_file_new_for_uri(g.directory);
|
||||||
gchar *parent = g_path_get_dirname(g.directory);
|
gchar *parent = parent_uri(directory);
|
||||||
|
g_object_unref(directory);
|
||||||
load_directory(parent);
|
load_directory(parent);
|
||||||
g_free(parent);
|
g_free(parent);
|
||||||
}
|
}
|
||||||
|
@ -896,7 +899,7 @@ on_button_press_browser_paned(
|
||||||
if (g.directory_forward)
|
if (g.directory_forward)
|
||||||
load_directory(g.directory_forward->data);
|
load_directory(g.directory_forward->data);
|
||||||
else
|
else
|
||||||
switch_to_view(g.path);
|
switch_to_view(g.uri);
|
||||||
return TRUE;
|
return TRUE;
|
||||||
default:
|
default:
|
||||||
return FALSE;
|
return FALSE;
|
||||||
|
@ -1245,7 +1248,7 @@ main(int argc, char *argv[])
|
||||||
if (size >= FIV_THUMBNAIL_SIZE_COUNT)
|
if (size >= FIV_THUMBNAIL_SIZE_COUNT)
|
||||||
exit_fatal("unknown thumbnail size: %s", thumbnail_size);
|
exit_fatal("unknown thumbnail size: %s", thumbnail_size);
|
||||||
|
|
||||||
GFile *target = g_file_new_for_path(path_arg);
|
GFile *target = g_file_new_for_commandline_arg(path_arg);
|
||||||
if (!fiv_thumbnail_produce(target, size, &error))
|
if (!fiv_thumbnail_produce(target, size, &error))
|
||||||
exit_fatal("%s", error->message);
|
exit_fatal("%s", error->message);
|
||||||
g_object_unref(target);
|
g_object_unref(target);
|
||||||
|
@ -1389,12 +1392,21 @@ main(int argc, char *argv[])
|
||||||
gtk_window_set_default_size(GTK_WINDOW(g.window), 4 * unit, 3 * unit);
|
gtk_window_set_default_size(GTK_WINDOW(g.window), 4 * unit, 3 * unit);
|
||||||
|
|
||||||
g.files = g_ptr_array_new_full(16, g_free);
|
g.files = g_ptr_array_new_full(16, g_free);
|
||||||
g.directory = g_get_current_dir();
|
gchar *cwd = g_get_current_dir();
|
||||||
|
g.directory = g_filename_to_uri(cwd, NULL, NULL /* error */);
|
||||||
|
g_free(cwd);
|
||||||
|
|
||||||
// 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);
|
||||||
if (!path_arg || !open_any_path(path_arg, browse))
|
|
||||||
open_any_path(g.directory, FALSE);
|
gchar *uri = NULL;
|
||||||
|
if (path_arg) {
|
||||||
|
GFile *file = g_file_new_for_commandline_arg(path_arg);
|
||||||
|
uri = g_file_get_uri(file);
|
||||||
|
g_object_unref(file);
|
||||||
|
}
|
||||||
|
if (!uri || !open_any_uri(uri, browse))
|
||||||
|
open_any_uri(g.directory, FALSE);
|
||||||
|
|
||||||
gtk_widget_show_all(g.window);
|
gtk_widget_show_all(g.window);
|
||||||
gtk_main();
|
gtk_main();
|
||||||
|
|
|
@ -3,7 +3,7 @@ Type=Application
|
||||||
Name=fastiv
|
Name=fastiv
|
||||||
GenericName=Image Viewer
|
GenericName=Image Viewer
|
||||||
Icon=fastiv
|
Icon=fastiv
|
||||||
Exec=fastiv -- %f
|
Exec=fastiv -- %u
|
||||||
Terminal=false
|
Terminal=false
|
||||||
StartupNotify=true
|
StartupNotify=true
|
||||||
Categories=Graphics;2DGraphics;Viewer;
|
Categories=Graphics;2DGraphics;Viewer;
|
||||||
|
|
|
@ -48,7 +48,7 @@ struct _FivBrowser {
|
||||||
int item_height; ///< Thumbnail height in pixels
|
int item_height; ///< Thumbnail height in pixels
|
||||||
int item_spacing; ///< Space between items in pixels
|
int item_spacing; ///< Space between items in pixels
|
||||||
|
|
||||||
char *path; ///< Current path
|
char *uri; ///< Current URI
|
||||||
GArray *entries; ///< []Entry
|
GArray *entries; ///< []Entry
|
||||||
GArray *layouted_rows; ///< []Row
|
GArray *layouted_rows; ///< []Row
|
||||||
int selected;
|
int selected;
|
||||||
|
@ -502,9 +502,9 @@ thumbnailer_next(FivBrowser *self)
|
||||||
|
|
||||||
const Entry *entry = link->data;
|
const Entry *entry = link->data;
|
||||||
GFile *file = g_file_new_for_uri(entry->uri);
|
GFile *file = g_file_new_for_uri(entry->uri);
|
||||||
gchar *path = g_file_get_path(file);
|
gchar *uri = g_file_get_uri(file);
|
||||||
g_object_unref(file);
|
g_object_unref(file);
|
||||||
if (!path) {
|
if (!uri) {
|
||||||
// TODO(p): Support thumbnailing non-local URIs in some manner.
|
// TODO(p): Support thumbnailing non-local URIs in some manner.
|
||||||
self->thumbnail_queue = g_list_delete_link(self->thumbnail_queue, link);
|
self->thumbnail_queue = g_list_delete_link(self->thumbnail_queue, link);
|
||||||
return;
|
return;
|
||||||
|
@ -513,9 +513,9 @@ thumbnailer_next(FivBrowser *self)
|
||||||
GError *error = NULL;
|
GError *error = NULL;
|
||||||
self->thumbnailer = g_subprocess_new(G_SUBPROCESS_FLAGS_NONE, &error,
|
self->thumbnailer = g_subprocess_new(G_SUBPROCESS_FLAGS_NONE, &error,
|
||||||
PROJECT_NAME, "--thumbnail",
|
PROJECT_NAME, "--thumbnail",
|
||||||
fiv_thumbnail_sizes[self->item_size].thumbnail_spec_name, "--", path,
|
fiv_thumbnail_sizes[self->item_size].thumbnail_spec_name, "--", uri,
|
||||||
NULL);
|
NULL);
|
||||||
g_free(path);
|
g_free(uri);
|
||||||
if (error) {
|
if (error) {
|
||||||
g_warning("%s", error->message);
|
g_warning("%s", error->message);
|
||||||
g_error_free(error);
|
g_error_free(error);
|
||||||
|
@ -550,7 +550,7 @@ thumbnailer_start(FivBrowser *self)
|
||||||
gchar *thumbnails_dir = fiv_thumbnail_get_root();
|
gchar *thumbnails_dir = fiv_thumbnail_get_root();
|
||||||
GFile *thumbnails = g_file_new_for_path(thumbnails_dir);
|
GFile *thumbnails = g_file_new_for_path(thumbnails_dir);
|
||||||
g_free(thumbnails_dir);
|
g_free(thumbnails_dir);
|
||||||
GFile *current = g_file_new_for_path(self->path);
|
GFile *current = g_file_new_for_uri(self->uri);
|
||||||
gboolean is_a_thumbnail = g_file_has_prefix(current, thumbnails);
|
gboolean is_a_thumbnail = g_file_has_prefix(current, thumbnails);
|
||||||
g_object_unref(current);
|
g_object_unref(current);
|
||||||
g_object_unref(thumbnails);
|
g_object_unref(thumbnails);
|
||||||
|
@ -759,7 +759,7 @@ fiv_browser_finalize(GObject *gobject)
|
||||||
{
|
{
|
||||||
FivBrowser *self = FIV_BROWSER(gobject);
|
FivBrowser *self = FIV_BROWSER(gobject);
|
||||||
thumbnailer_abort(self);
|
thumbnailer_abort(self);
|
||||||
g_free(self->path);
|
g_free(self->uri);
|
||||||
g_array_free(self->entries, TRUE);
|
g_array_free(self->entries, TRUE);
|
||||||
g_array_free(self->layouted_rows, TRUE);
|
g_array_free(self->layouted_rows, TRUE);
|
||||||
cairo_surface_destroy(self->glow);
|
cairo_surface_destroy(self->glow);
|
||||||
|
@ -940,9 +940,7 @@ fiv_browser_button_press_event(GtkWidget *widget, GdkEventButton *event)
|
||||||
|
|
||||||
const Entry *entry = entry_at(self, event->x, event->y);
|
const Entry *entry = entry_at(self, event->x, event->y);
|
||||||
if (!entry && event->button == GDK_BUTTON_SECONDARY) {
|
if (!entry && event->button == GDK_BUTTON_SECONDARY) {
|
||||||
gchar *uri = g_filename_to_uri(self->path, NULL, NULL);
|
show_context_menu(widget, self->uri);
|
||||||
show_context_menu(widget, uri);
|
|
||||||
g_free(uri);
|
|
||||||
return TRUE;
|
return TRUE;
|
||||||
}
|
}
|
||||||
if (!entry)
|
if (!entry)
|
||||||
|
@ -1175,22 +1173,27 @@ entry_compare(gconstpointer a, gconstpointer b)
|
||||||
|
|
||||||
void
|
void
|
||||||
fiv_browser_load(
|
fiv_browser_load(
|
||||||
FivBrowser *self, FivBrowserFilterCallback cb, const char *path)
|
FivBrowser *self, FivBrowserFilterCallback cb, const char *uri)
|
||||||
{
|
{
|
||||||
g_return_if_fail(FIV_IS_BROWSER(self));
|
g_return_if_fail(FIV_IS_BROWSER(self));
|
||||||
|
|
||||||
thumbnailer_abort(self);
|
thumbnailer_abort(self);
|
||||||
g_array_set_size(self->entries, 0);
|
g_array_set_size(self->entries, 0);
|
||||||
g_array_set_size(self->layouted_rows, 0);
|
g_array_set_size(self->layouted_rows, 0);
|
||||||
g_clear_pointer(&self->path, g_free);
|
g_clear_pointer(&self->uri, g_free);
|
||||||
|
|
||||||
GFile *file = g_file_new_for_path((self->path = g_strdup(path)));
|
GFile *file = g_file_new_for_uri((self->uri = g_strdup(uri)));
|
||||||
|
|
||||||
|
GError *error = NULL;
|
||||||
GFileEnumerator *enumerator = g_file_enumerate_children(file,
|
GFileEnumerator *enumerator = g_file_enumerate_children(file,
|
||||||
G_FILE_ATTRIBUTE_STANDARD_NAME "," G_FILE_ATTRIBUTE_STANDARD_TYPE,
|
G_FILE_ATTRIBUTE_STANDARD_NAME "," G_FILE_ATTRIBUTE_STANDARD_TYPE,
|
||||||
G_FILE_QUERY_INFO_NONE, NULL, NULL);
|
G_FILE_QUERY_INFO_NONE, NULL, &error);
|
||||||
g_object_unref(file);
|
g_object_unref(file);
|
||||||
if (!enumerator)
|
if (!enumerator) {
|
||||||
|
// Note that this has had a side-effect of clearing all entries.
|
||||||
|
g_error_free(error);
|
||||||
return;
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
while (TRUE) {
|
while (TRUE) {
|
||||||
GFileInfo *info = NULL;
|
GFileInfo *info = NULL;
|
||||||
|
|
|
@ -33,7 +33,9 @@ static void
|
||||||
one_file(const char *filename)
|
one_file(const char *filename)
|
||||||
{
|
{
|
||||||
double since_us = timestamp();
|
double since_us = timestamp();
|
||||||
cairo_surface_t *loaded_by_us = fiv_io_open(filename, NULL);
|
gchar *uri = g_filename_to_uri(filename, NULL, NULL);
|
||||||
|
cairo_surface_t *loaded_by_us = fiv_io_open(uri, NULL, FALSE, NULL);
|
||||||
|
g_free(uri);
|
||||||
if (!loaded_by_us)
|
if (!loaded_by_us)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
|
46
fiv-io.c
46
fiv-io.c
|
@ -1329,9 +1329,9 @@ open_libraw(const gchar *data, gsize len, GError **error)
|
||||||
|
|
||||||
// FIXME: librsvg rasterizes filters, so this method isn't fully appropriate.
|
// FIXME: librsvg rasterizes filters, so this method isn't fully appropriate.
|
||||||
static cairo_surface_t *
|
static cairo_surface_t *
|
||||||
open_librsvg(const gchar *data, gsize len, const gchar *path, GError **error)
|
open_librsvg(const gchar *data, gsize len, const gchar *uri, GError **error)
|
||||||
{
|
{
|
||||||
GFile *base_file = g_file_new_for_path(path);
|
GFile *base_file = g_file_new_for_uri(uri);
|
||||||
GInputStream *is = g_memory_input_stream_new_from_data(data, len, NULL);
|
GInputStream *is = g_memory_input_stream_new_from_data(data, len, NULL);
|
||||||
RsvgHandle *handle = rsvg_handle_new_from_stream_sync(
|
RsvgHandle *handle = rsvg_handle_new_from_stream_sync(
|
||||||
is, base_file, RSVG_HANDLE_FLAG_KEEP_IMAGE_DATA, NULL, error);
|
is, base_file, RSVG_HANDLE_FLAG_KEEP_IMAGE_DATA, NULL, error);
|
||||||
|
@ -1694,7 +1694,7 @@ fail:
|
||||||
}
|
}
|
||||||
|
|
||||||
static cairo_surface_t *
|
static cairo_surface_t *
|
||||||
open_libwebp(const gchar *data, gsize len, const gchar *path,
|
open_libwebp(const gchar *data, gsize len, const gchar *uri,
|
||||||
FivIoProfile target, GError **error)
|
FivIoProfile target, GError **error)
|
||||||
{
|
{
|
||||||
// It is wholly zero-initialized by libwebp.
|
// It is wholly zero-initialized by libwebp.
|
||||||
|
@ -1723,7 +1723,7 @@ open_libwebp(const gchar *data, gsize len, const gchar *path,
|
||||||
WebPDemuxState state = WEBP_DEMUX_PARSE_ERROR;
|
WebPDemuxState state = WEBP_DEMUX_PARSE_ERROR;
|
||||||
WebPDemuxer *demux = WebPDemuxPartial(&wd, &state);
|
WebPDemuxer *demux = WebPDemuxPartial(&wd, &state);
|
||||||
if (!demux) {
|
if (!demux) {
|
||||||
g_warning("%s: %s", path, "demux failure");
|
g_warning("%s: %s", uri, "demux failure");
|
||||||
goto fail;
|
goto fail;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1876,7 +1876,7 @@ fail:
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
load_libheif_aux_images(const gchar *path, struct heif_image_handle *top,
|
load_libheif_aux_images(const gchar *uri, struct heif_image_handle *top,
|
||||||
cairo_surface_t **result, cairo_surface_t **result_tail)
|
cairo_surface_t **result, cairo_surface_t **result_tail)
|
||||||
{
|
{
|
||||||
// Include the depth image, we have no special processing for it now.
|
// Include the depth image, we have no special processing for it now.
|
||||||
|
@ -1890,14 +1890,14 @@ load_libheif_aux_images(const gchar *path, struct heif_image_handle *top,
|
||||||
struct heif_error err =
|
struct heif_error err =
|
||||||
heif_image_handle_get_auxiliary_image_handle(top, ids[i], &handle);
|
heif_image_handle_get_auxiliary_image_handle(top, ids[i], &handle);
|
||||||
if (err.code != heif_error_Ok) {
|
if (err.code != heif_error_Ok) {
|
||||||
g_warning("%s: %s", path, err.message);
|
g_warning("%s: %s", uri, err.message);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
GError *e = NULL;
|
GError *e = NULL;
|
||||||
if (!try_append_page(
|
if (!try_append_page(
|
||||||
load_libheif_image(handle, &e), result, result_tail)) {
|
load_libheif_image(handle, &e), result, result_tail)) {
|
||||||
g_warning("%s: %s", path, e->message);
|
g_warning("%s: %s", uri, e->message);
|
||||||
g_error_free(e);
|
g_error_free(e);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1908,7 +1908,7 @@ load_libheif_aux_images(const gchar *path, struct heif_image_handle *top,
|
||||||
}
|
}
|
||||||
|
|
||||||
static cairo_surface_t *
|
static cairo_surface_t *
|
||||||
open_libheif(const gchar *data, gsize len, const gchar *path,
|
open_libheif(const gchar *data, gsize len, const gchar *uri,
|
||||||
FivIoProfile profile, GError **error)
|
FivIoProfile profile, GError **error)
|
||||||
{
|
{
|
||||||
// libheif will throw C++ exceptions on allocation failures.
|
// libheif will throw C++ exceptions on allocation failures.
|
||||||
|
@ -1930,19 +1930,19 @@ open_libheif(const gchar *data, gsize len, const gchar *path,
|
||||||
struct heif_image_handle *handle = NULL;
|
struct heif_image_handle *handle = NULL;
|
||||||
err = heif_context_get_image_handle(ctx, ids[i], &handle);
|
err = heif_context_get_image_handle(ctx, ids[i], &handle);
|
||||||
if (err.code != heif_error_Ok) {
|
if (err.code != heif_error_Ok) {
|
||||||
g_warning("%s: %s", path, err.message);
|
g_warning("%s: %s", uri, err.message);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
GError *e = NULL;
|
GError *e = NULL;
|
||||||
if (!try_append_page(
|
if (!try_append_page(
|
||||||
load_libheif_image(handle, &e), &result, &result_tail)) {
|
load_libheif_image(handle, &e), &result, &result_tail)) {
|
||||||
g_warning("%s: %s", path, e->message);
|
g_warning("%s: %s", uri, e->message);
|
||||||
g_error_free(e);
|
g_error_free(e);
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO(p): Possibly add thumbnail images as well.
|
// TODO(p): Possibly add thumbnail images as well.
|
||||||
load_libheif_aux_images(path, handle, &result, &result_tail);
|
load_libheif_aux_images(uri, handle, &result, &result_tail);
|
||||||
heif_image_handle_release(handle);
|
heif_image_handle_release(handle);
|
||||||
}
|
}
|
||||||
if (!result) {
|
if (!result) {
|
||||||
|
@ -2136,7 +2136,7 @@ fail:
|
||||||
}
|
}
|
||||||
|
|
||||||
static cairo_surface_t *
|
static cairo_surface_t *
|
||||||
open_libtiff(const gchar *data, gsize len, const gchar *path,
|
open_libtiff(const gchar *data, gsize len, const gchar *uri,
|
||||||
FivIoProfile target, GError **error)
|
FivIoProfile target, GError **error)
|
||||||
{
|
{
|
||||||
// Both kinds of handlers are called, redirect everything.
|
// Both kinds of handlers are called, redirect everything.
|
||||||
|
@ -2151,7 +2151,7 @@ open_libtiff(const gchar *data, gsize len, const gchar *path,
|
||||||
};
|
};
|
||||||
|
|
||||||
cairo_surface_t *result = NULL, *result_tail = NULL;
|
cairo_surface_t *result = NULL, *result_tail = NULL;
|
||||||
TIFF *tiff = TIFFClientOpen(path, "rm" /* Avoid mmap. */, &h,
|
TIFF *tiff = TIFFClientOpen(uri, "rm" /* Avoid mmap. */, &h,
|
||||||
fiv_io_tiff_read, fiv_io_tiff_write, fiv_io_tiff_seek,
|
fiv_io_tiff_read, fiv_io_tiff_write, fiv_io_tiff_seek,
|
||||||
fiv_io_tiff_close, fiv_io_tiff_size, NULL, NULL);
|
fiv_io_tiff_close, fiv_io_tiff_size, NULL, NULL);
|
||||||
if (!tiff)
|
if (!tiff)
|
||||||
|
@ -2186,7 +2186,7 @@ open_libtiff(const gchar *data, gsize len, const gchar *path,
|
||||||
GError *err = NULL;
|
GError *err = NULL;
|
||||||
if (!try_append_page(
|
if (!try_append_page(
|
||||||
load_libtiff_directory(tiff, &err), &result, &result_tail)) {
|
load_libtiff_directory(tiff, &err), &result, &result_tail)) {
|
||||||
g_warning("%s: %s", path, err->message);
|
g_warning("%s: %s", uri, err->message);
|
||||||
g_error_free(err);
|
g_error_free(err);
|
||||||
}
|
}
|
||||||
} while (TIFFReadDirectory(tiff));
|
} while (TIFFReadDirectory(tiff));
|
||||||
|
@ -2316,7 +2316,7 @@ cairo_user_data_key_t fiv_io_key_page_previous;
|
||||||
|
|
||||||
cairo_surface_t *
|
cairo_surface_t *
|
||||||
fiv_io_open(
|
fiv_io_open(
|
||||||
const gchar *path, FivIoProfile profile, gboolean enhance, GError **error)
|
const gchar *uri, FivIoProfile profile, gboolean enhance, GError **error)
|
||||||
{
|
{
|
||||||
// TODO(p): Don't always load everything into memory, test type first,
|
// TODO(p): Don't always load everything into memory, test type first,
|
||||||
// so that we can reject non-pictures early. Wuffs only needs the first
|
// so that we can reject non-pictures early. Wuffs only needs the first
|
||||||
|
@ -2332,19 +2332,21 @@ fiv_io_open(
|
||||||
//
|
//
|
||||||
// gdk-pixbuf exposes its detection data through gdk_pixbuf_get_formats().
|
// gdk-pixbuf exposes its detection data through gdk_pixbuf_get_formats().
|
||||||
// This may also be unbounded, as per format_check().
|
// This may also be unbounded, as per format_check().
|
||||||
|
GFile *file = g_file_new_for_uri(uri);
|
||||||
|
|
||||||
gchar *data = NULL;
|
gchar *data = NULL;
|
||||||
gsize len = 0;
|
gsize len = 0;
|
||||||
if (!g_file_get_contents(path, &data, &len, error))
|
if (!g_file_load_contents(file, NULL, &data, &len, NULL, error))
|
||||||
return NULL;
|
return NULL;
|
||||||
|
|
||||||
cairo_surface_t *surface =
|
cairo_surface_t *surface =
|
||||||
fiv_io_open_from_data(data, len, path, profile, enhance, error);
|
fiv_io_open_from_data(data, len, uri, profile, enhance, error);
|
||||||
free(data);
|
free(data);
|
||||||
return surface;
|
return surface;
|
||||||
}
|
}
|
||||||
|
|
||||||
cairo_surface_t *
|
cairo_surface_t *
|
||||||
fiv_io_open_from_data(const char *data, size_t len, const gchar *path,
|
fiv_io_open_from_data(const char *data, size_t len, const gchar *uri,
|
||||||
FivIoProfile profile, gboolean enhance, GError **error)
|
FivIoProfile profile, gboolean enhance, GError **error)
|
||||||
{
|
{
|
||||||
wuffs_base__slice_u8 prefix =
|
wuffs_base__slice_u8 prefix =
|
||||||
|
@ -2376,7 +2378,7 @@ fiv_io_open_from_data(const char *data, size_t len, const gchar *path,
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
// TODO(p): https://github.com/google/wuffs/commit/4c04ac1
|
// TODO(p): https://github.com/google/wuffs/commit/4c04ac1
|
||||||
if ((surface = open_libwebp(data, len, path, profile, error)))
|
if ((surface = open_libwebp(data, len, uri, profile, error)))
|
||||||
break;
|
break;
|
||||||
if (error) {
|
if (error) {
|
||||||
g_debug("%s", (*error)->message);
|
g_debug("%s", (*error)->message);
|
||||||
|
@ -2395,7 +2397,7 @@ fiv_io_open_from_data(const char *data, size_t len, const gchar *path,
|
||||||
}
|
}
|
||||||
#endif // HAVE_LIBRAW ---------------------------------------------------------
|
#endif // HAVE_LIBRAW ---------------------------------------------------------
|
||||||
#ifdef HAVE_LIBRSVG // --------------------------------------------------------
|
#ifdef HAVE_LIBRSVG // --------------------------------------------------------
|
||||||
if ((surface = open_librsvg(data, len, path, error)))
|
if ((surface = open_librsvg(data, len, uri, error)))
|
||||||
break;
|
break;
|
||||||
|
|
||||||
// XXX: It doesn't look like librsvg can return sensible errors.
|
// XXX: It doesn't look like librsvg can return sensible errors.
|
||||||
|
@ -2413,7 +2415,7 @@ fiv_io_open_from_data(const char *data, size_t len, const gchar *path,
|
||||||
}
|
}
|
||||||
#endif // HAVE_XCURSOR --------------------------------------------------------
|
#endif // HAVE_XCURSOR --------------------------------------------------------
|
||||||
#ifdef HAVE_LIBHEIF //---------------------------------------------------------
|
#ifdef HAVE_LIBHEIF //---------------------------------------------------------
|
||||||
if ((surface = open_libheif(data, len, path, profile, error)))
|
if ((surface = open_libheif(data, len, uri, profile, error)))
|
||||||
break;
|
break;
|
||||||
if (error) {
|
if (error) {
|
||||||
g_debug("%s", (*error)->message);
|
g_debug("%s", (*error)->message);
|
||||||
|
@ -2422,7 +2424,7 @@ fiv_io_open_from_data(const char *data, size_t len, const gchar *path,
|
||||||
#endif // HAVE_LIBHEIF --------------------------------------------------------
|
#endif // HAVE_LIBHEIF --------------------------------------------------------
|
||||||
#ifdef HAVE_LIBTIFF //---------------------------------------------------------
|
#ifdef HAVE_LIBTIFF //---------------------------------------------------------
|
||||||
// This needs to be positioned after LibRaw.
|
// This needs to be positioned after LibRaw.
|
||||||
if ((surface = open_libtiff(data, len, path, profile, error)))
|
if ((surface = open_libtiff(data, len, uri, profile, error)))
|
||||||
break;
|
break;
|
||||||
if (error) {
|
if (error) {
|
||||||
g_debug("%s", (*error)->message);
|
g_debug("%s", (*error)->message);
|
||||||
|
|
4
fiv-io.h
4
fiv-io.h
|
@ -73,9 +73,9 @@ extern cairo_user_data_key_t fiv_io_key_page_next;
|
||||||
extern cairo_user_data_key_t fiv_io_key_page_previous;
|
extern cairo_user_data_key_t fiv_io_key_page_previous;
|
||||||
|
|
||||||
cairo_surface_t *fiv_io_open(
|
cairo_surface_t *fiv_io_open(
|
||||||
const gchar *path, FivIoProfile profile, gboolean enhance, GError **error);
|
const gchar *uri, FivIoProfile profile, gboolean enhance, GError **error);
|
||||||
cairo_surface_t *fiv_io_open_from_data(const char *data, size_t len,
|
cairo_surface_t *fiv_io_open_from_data(const char *data, size_t len,
|
||||||
const gchar *path, FivIoProfile profile, gboolean enhance, GError **error);
|
const gchar *uri, FivIoProfile profile, gboolean enhance, GError **error);
|
||||||
|
|
||||||
int fiv_io_filecmp(GFile *f1, GFile *f2);
|
int fiv_io_filecmp(GFile *f1, GFile *f2);
|
||||||
|
|
||||||
|
|
|
@ -85,11 +85,15 @@ static GtkWidget *
|
||||||
create_row(GFile *file, const char *icon_name)
|
create_row(GFile *file, const char *icon_name)
|
||||||
{
|
{
|
||||||
// TODO(p): Handle errors better.
|
// TODO(p): Handle errors better.
|
||||||
|
GError *error = NULL;
|
||||||
GFileInfo *info =
|
GFileInfo *info =
|
||||||
g_file_query_info(file, G_FILE_ATTRIBUTE_STANDARD_DISPLAY_NAME,
|
g_file_query_info(file, G_FILE_ATTRIBUTE_STANDARD_DISPLAY_NAME,
|
||||||
G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS, NULL, NULL);
|
G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS, NULL, &error);
|
||||||
if (!info)
|
if (!info) {
|
||||||
|
g_debug("%s", error->message);
|
||||||
|
g_error_free(error);
|
||||||
return NULL;
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
const char *name = g_file_info_get_display_name(info);
|
const char *name = g_file_info_get_display_name(info);
|
||||||
GtkWidget *rowbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0);
|
GtkWidget *rowbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0);
|
||||||
|
@ -147,20 +151,21 @@ update_location(FivSidebar *self, GFile *location)
|
||||||
g_return_if_fail(self->location != NULL);
|
g_return_if_fail(self->location != NULL);
|
||||||
|
|
||||||
GFile *iter = g_object_ref(self->location);
|
GFile *iter = g_object_ref(self->location);
|
||||||
|
GtkWidget *row = NULL;
|
||||||
while (TRUE) {
|
while (TRUE) {
|
||||||
GFile *parent = g_file_get_parent(iter);
|
GFile *parent = g_file_get_parent(iter);
|
||||||
g_object_unref(iter);
|
g_object_unref(iter);
|
||||||
if (!(iter = parent))
|
if (!(iter = parent))
|
||||||
break;
|
break;
|
||||||
|
|
||||||
gtk_list_box_prepend(GTK_LIST_BOX(self->listbox),
|
if ((row = create_row(parent, "go-up-symbolic")))
|
||||||
create_row(parent, "go-up-symbolic"));
|
gtk_list_box_prepend(GTK_LIST_BOX(self->listbox), row);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Other options are "folder-{visiting,open}-symbolic", though the former
|
// Other options are "folder-{visiting,open}-symbolic", though the former
|
||||||
// is mildly inappropriate (means: open in another window).
|
// is mildly inappropriate (means: open in another window).
|
||||||
gtk_container_add(GTK_CONTAINER(self->listbox),
|
if ((row = create_row(self->location, "circle-filled-symbolic")))
|
||||||
create_row(self->location, "circle-filled-symbolic"));
|
gtk_container_add(GTK_CONTAINER(self->listbox), row);
|
||||||
|
|
||||||
GFileEnumerator *enumerator = g_file_enumerate_children(self->location,
|
GFileEnumerator *enumerator = g_file_enumerate_children(self->location,
|
||||||
G_FILE_ATTRIBUTE_STANDARD_DISPLAY_NAME
|
G_FILE_ATTRIBUTE_STANDARD_DISPLAY_NAME
|
||||||
|
@ -181,9 +186,9 @@ update_location(FivSidebar *self, GFile *location)
|
||||||
break;
|
break;
|
||||||
|
|
||||||
if (g_file_info_get_file_type(info) == G_FILE_TYPE_DIRECTORY &&
|
if (g_file_info_get_file_type(info) == G_FILE_TYPE_DIRECTORY &&
|
||||||
!g_file_info_get_is_hidden(info))
|
!g_file_info_get_is_hidden(info) &&
|
||||||
gtk_container_add(GTK_CONTAINER(self->listbox),
|
(row = create_row(child, "go-down-symbolic")))
|
||||||
create_row(child, "go-down-symbolic"));
|
gtk_container_add(GTK_CONTAINER(self->listbox), row);
|
||||||
}
|
}
|
||||||
g_object_unref(enumerator);
|
g_object_unref(enumerator);
|
||||||
}
|
}
|
||||||
|
|
|
@ -223,20 +223,17 @@ fiv_thumbnail_produce(GFile *target, FivThumbnailSize max_size, GError **error)
|
||||||
max_size <= FIV_THUMBNAIL_SIZE_MAX, FALSE);
|
max_size <= FIV_THUMBNAIL_SIZE_MAX, FALSE);
|
||||||
|
|
||||||
// Local files only, at least for now.
|
// Local files only, at least for now.
|
||||||
gchar *path = g_file_get_path(target);
|
const gchar *path = g_file_peek_path(target);
|
||||||
if (!path)
|
if (!path)
|
||||||
return FALSE;
|
return FALSE;
|
||||||
|
|
||||||
GMappedFile *mf = g_mapped_file_new(path, FALSE, error);
|
GMappedFile *mf = g_mapped_file_new(path, FALSE, error);
|
||||||
if (!mf) {
|
if (!mf)
|
||||||
g_free(path);
|
|
||||||
return FALSE;
|
return FALSE;
|
||||||
}
|
|
||||||
|
|
||||||
GStatBuf st = {};
|
GStatBuf st = {};
|
||||||
if (g_stat(path, &st)) {
|
if (g_stat(path, &st)) {
|
||||||
set_error(error, g_strerror(errno));
|
set_error(error, g_strerror(errno));
|
||||||
g_free(path);
|
|
||||||
return FALSE;
|
return FALSE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -246,7 +243,6 @@ fiv_thumbnail_produce(GFile *target, FivThumbnailSize max_size, GError **error)
|
||||||
cairo_surface_t *surface = fiv_io_open_from_data(
|
cairo_surface_t *surface = fiv_io_open_from_data(
|
||||||
g_mapped_file_get_contents(mf), filesize, path, sRGB, FALSE, error);
|
g_mapped_file_get_contents(mf), filesize, path, sRGB, FALSE, error);
|
||||||
|
|
||||||
g_free(path);
|
|
||||||
g_mapped_file_unref(mf);
|
g_mapped_file_unref(mf);
|
||||||
if (sRGB)
|
if (sRGB)
|
||||||
fiv_io_profile_free(sRGB);
|
fiv_io_profile_free(sRGB);
|
||||||
|
@ -329,7 +325,12 @@ static cairo_surface_t *
|
||||||
read_wide_thumbnail(
|
read_wide_thumbnail(
|
||||||
const gchar *path, const gchar *uri, time_t mtime, GError **error)
|
const gchar *path, const gchar *uri, time_t mtime, GError **error)
|
||||||
{
|
{
|
||||||
cairo_surface_t *surface = fiv_io_open(path, NULL, FALSE, error);
|
gchar *thumbnail_uri = g_filename_to_uri(path, NULL, error);
|
||||||
|
if (!thumbnail_uri)
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
cairo_surface_t *surface = fiv_io_open(thumbnail_uri, NULL, FALSE, error);
|
||||||
|
g_free(thumbnail_uri);
|
||||||
if (!surface)
|
if (!surface)
|
||||||
return NULL;
|
return NULL;
|
||||||
|
|
||||||
|
@ -496,14 +497,9 @@ fiv_thumbnail_lookup(GFile *target, FivThumbnailSize size)
|
||||||
size <= FIV_THUMBNAIL_SIZE_MAX, NULL);
|
size <= FIV_THUMBNAIL_SIZE_MAX, NULL);
|
||||||
|
|
||||||
// Local files only, at least for now.
|
// Local files only, at least for now.
|
||||||
gchar *path = g_file_get_path(target);
|
|
||||||
if (!path)
|
|
||||||
return NULL;
|
|
||||||
|
|
||||||
GStatBuf st = {};
|
GStatBuf st = {};
|
||||||
int err = g_stat(path, &st);
|
const gchar *path = g_file_peek_path(target);
|
||||||
g_free(path);
|
if (!path || g_stat(path, &st))
|
||||||
if (err)
|
|
||||||
return NULL;
|
return NULL;
|
||||||
|
|
||||||
gchar *uri = g_file_get_uri(target);
|
gchar *uri = g_file_get_uri(target);
|
||||||
|
|
37
fiv-view.c
37
fiv-view.c
|
@ -33,7 +33,7 @@
|
||||||
|
|
||||||
struct _FivView {
|
struct _FivView {
|
||||||
GtkWidget parent_instance;
|
GtkWidget parent_instance;
|
||||||
gchar *path; ///< Path to the current image (if any)
|
gchar *uri; ///< Path to the current image (if any)
|
||||||
cairo_surface_t *image; ///< The loaded image (sequence)
|
cairo_surface_t *image; ///< The loaded image (sequence)
|
||||||
cairo_surface_t *page; ///< Current page within image, weak
|
cairo_surface_t *page; ///< Current page within image, weak
|
||||||
cairo_surface_t *frame; ///< Current frame within page, weak
|
cairo_surface_t *frame; ///< Current frame within page, weak
|
||||||
|
@ -119,7 +119,7 @@ fiv_view_finalize(GObject *gobject)
|
||||||
g_clear_pointer(&self->screen_cms_profile, fiv_io_profile_free);
|
g_clear_pointer(&self->screen_cms_profile, fiv_io_profile_free);
|
||||||
g_clear_pointer(&self->enhance_swap, cairo_surface_destroy);
|
g_clear_pointer(&self->enhance_swap, cairo_surface_destroy);
|
||||||
g_clear_pointer(&self->image, cairo_surface_destroy);
|
g_clear_pointer(&self->image, cairo_surface_destroy);
|
||||||
g_free(self->path);
|
g_free(self->uri);
|
||||||
|
|
||||||
G_OBJECT_CLASS(fiv_view_parent_class)->finalize(gobject);
|
G_OBJECT_CLASS(fiv_view_parent_class)->finalize(gobject);
|
||||||
}
|
}
|
||||||
|
@ -754,19 +754,25 @@ save_as(FivView *self, cairo_surface_t *frame)
|
||||||
GtkFileChooser *chooser = GTK_FILE_CHOOSER(dialog);
|
GtkFileChooser *chooser = GTK_FILE_CHOOSER(dialog);
|
||||||
gtk_file_chooser_set_do_overwrite_confirmation(chooser, TRUE);
|
gtk_file_chooser_set_do_overwrite_confirmation(chooser, TRUE);
|
||||||
|
|
||||||
|
GFile *file = g_file_new_for_uri(self->uri);
|
||||||
|
const gchar *path = g_file_peek_path(file);
|
||||||
|
// TODO(p): Use g_file_info_get_display_name().
|
||||||
|
gchar *basename = g_filename_display_basename(path ? path : self->uri);
|
||||||
|
|
||||||
// Note that GTK+'s save dialog is too stupid to automatically change
|
// Note that GTK+'s save dialog is too stupid to automatically change
|
||||||
// the extension when user changes the filter. Presumably,
|
// the extension when user changes the filter. Presumably,
|
||||||
// gtk_file_chooser_set_extra_widget() can be used to circumvent this.
|
// gtk_file_chooser_set_extra_widget() can be used to circumvent this.
|
||||||
gchar *basename = g_filename_display_basename(self->path);
|
|
||||||
gchar *name =
|
gchar *name =
|
||||||
g_strdup_printf(frame ? "%s.frame.webp" : "%s.webp", basename);
|
g_strdup_printf(frame ? "%s.frame.webp" : "%s.webp", basename);
|
||||||
g_free(basename);
|
|
||||||
gtk_file_chooser_set_current_name(chooser, name);
|
gtk_file_chooser_set_current_name(chooser, name);
|
||||||
g_free(name);
|
g_free(name);
|
||||||
|
if (path) {
|
||||||
gchar *dirname = g_path_get_dirname(self->path);
|
gchar *dirname = g_path_get_dirname(path);
|
||||||
gtk_file_chooser_set_current_folder(chooser, dirname);
|
gtk_file_chooser_set_current_folder(chooser, dirname);
|
||||||
g_free(dirname);
|
g_free(dirname);
|
||||||
|
}
|
||||||
|
g_free(basename);
|
||||||
|
g_object_unref(file);
|
||||||
|
|
||||||
// This is the best general format: supports lossless encoding, animations,
|
// This is the best general format: supports lossless encoding, animations,
|
||||||
// alpha channel, and Exif and ICC profile metadata.
|
// alpha channel, and Exif and ICC profile metadata.
|
||||||
|
@ -892,9 +898,16 @@ info(FivView *self)
|
||||||
int flags = G_SUBPROCESS_FLAGS_STDOUT_PIPE | G_SUBPROCESS_FLAGS_STDERR_PIPE;
|
int flags = G_SUBPROCESS_FLAGS_STDOUT_PIPE | G_SUBPROCESS_FLAGS_STDERR_PIPE;
|
||||||
|
|
||||||
GError *error = NULL;
|
GError *error = NULL;
|
||||||
|
gchar *path = g_filename_from_uri(self->uri, NULL, &error);
|
||||||
|
if (!path) {
|
||||||
|
show_error_dialog(window, error);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
GSubprocess *subprocess = g_subprocess_new(flags, &error, "exiftool",
|
GSubprocess *subprocess = g_subprocess_new(flags, &error, "exiftool",
|
||||||
"-tab", "-groupNames", "-duplicates", "-extractEmbedded", "--binary",
|
"-tab", "-groupNames", "-duplicates", "-extractEmbedded", "--binary",
|
||||||
"-quiet", "--", self->path, NULL);
|
"-quiet", "--", path, NULL);
|
||||||
|
g_free(path);
|
||||||
if (error) {
|
if (error) {
|
||||||
show_error_dialog(window, error);
|
show_error_dialog(window, error);
|
||||||
return;
|
return;
|
||||||
|
@ -1123,10 +1136,10 @@ fiv_view_init(FivView *self)
|
||||||
|
|
||||||
// TODO(p): Progressive picture loading, or at least async/cancellable.
|
// TODO(p): Progressive picture loading, or at least async/cancellable.
|
||||||
gboolean
|
gboolean
|
||||||
fiv_view_open(FivView *self, const gchar *path, GError **error)
|
fiv_view_open(FivView *self, const gchar *uri, GError **error)
|
||||||
{
|
{
|
||||||
cairo_surface_t *surface = fiv_io_open(
|
cairo_surface_t *surface = fiv_io_open(
|
||||||
path, self->enable_cms ? self->screen_cms_profile : NULL, FALSE, error);
|
uri, self->enable_cms ? self->screen_cms_profile : NULL, FALSE, error);
|
||||||
if (!surface)
|
if (!surface)
|
||||||
return FALSE;
|
return FALSE;
|
||||||
if (self->image)
|
if (self->image)
|
||||||
|
@ -1145,8 +1158,8 @@ fiv_view_open(FivView *self, const gchar *path, GError **error)
|
||||||
switch_page(self, self->image);
|
switch_page(self, self->image);
|
||||||
set_scale_to_fit(self, true);
|
set_scale_to_fit(self, true);
|
||||||
|
|
||||||
g_free(self->path);
|
g_free(self->uri);
|
||||||
self->path = g_strdup(path);
|
self->uri = g_strdup(uri);
|
||||||
|
|
||||||
g_object_notify_by_pspec(G_OBJECT(self), view_properties[PROP_HAS_IMAGE]);
|
g_object_notify_by_pspec(G_OBJECT(self), view_properties[PROP_HAS_IMAGE]);
|
||||||
return TRUE;
|
return TRUE;
|
||||||
|
@ -1177,7 +1190,7 @@ static gboolean
|
||||||
reload(FivView *self)
|
reload(FivView *self)
|
||||||
{
|
{
|
||||||
GError *error = NULL;
|
GError *error = NULL;
|
||||||
cairo_surface_t *surface = fiv_io_open(self->path,
|
cairo_surface_t *surface = fiv_io_open(self->uri,
|
||||||
self->enable_cms ? self->screen_cms_profile : NULL, self->enhance,
|
self->enable_cms ? self->screen_cms_profile : NULL, self->enhance,
|
||||||
&error);
|
&error);
|
||||||
if (!surface) {
|
if (!surface) {
|
||||||
|
|
|
@ -23,7 +23,7 @@
|
||||||
G_DECLARE_FINAL_TYPE(FivView, fiv_view, FIV, VIEW, GtkWidget)
|
G_DECLARE_FINAL_TYPE(FivView, fiv_view, FIV, VIEW, GtkWidget)
|
||||||
|
|
||||||
/// Try to open the given file, synchronously, to be displayed by the widget.
|
/// Try to open the given file, synchronously, to be displayed by the widget.
|
||||||
gboolean fiv_view_open(FivView *self, const gchar *path, GError **error);
|
gboolean fiv_view_open(FivView *self, const gchar *uri, GError **error);
|
||||||
|
|
||||||
typedef enum _FivViewCommand {
|
typedef enum _FivViewCommand {
|
||||||
FIV_VIEW_COMMAND_ROTATE_LEFT = 1,
|
FIV_VIEW_COMMAND_ROTATE_LEFT = 1,
|
||||||
|
|
Loading…
Reference in New Issue