diff options
Diffstat (limited to 'fiv.c')
| -rw-r--r-- | fiv.c | 192 |
1 files changed, 176 insertions, 16 deletions
@@ -1,7 +1,7 @@ // // fiv.c: fuck-if-I-know-how-to-name-it image browser and viewer // -// Copyright (c) 2021 - 2024, Přemysl Eric Janouch <p@janouch.name> +// Copyright (c) 2021 - 2025, 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. @@ -73,6 +73,138 @@ slist_to_strv(GSList *slist) return strv; } +// --- macOS utilities --------------------------------------------------------- + +#ifdef __APPLE__ +#include <CoreFoundation/CoreFoundation.h> + +static gchar * +cfurlref_to_path(CFURLRef urlref) +{ + CFStringRef path = CFURLCopyFileSystemPath(urlref, kCFURLPOSIXPathStyle); + if (!path) + return NULL; + + CFIndex size = CFStringGetMaximumSizeForEncoding( + CFStringGetLength(path), kCFStringEncodingUTF8) + 1; + gchar *string = g_malloc(size); + + Boolean ok = CFStringGetCString(path, string, size, kCFStringEncodingUTF8); + CFRelease(path); + if (!ok) { + g_free(string); + return NULL; + } + return string; +} + +static gchar * +get_application_bundle_path(void) +{ + gchar *result = NULL; + CFBundleRef bundle = CFBundleGetMainBundle(); + if (!bundle) + goto fail_1; + + // When launched from outside a bundle, it will make up one, + // but these paths will then be equal. + CFURLRef bundle_url = CFBundleCopyBundleURL(bundle); + if (!bundle_url) + goto fail_1; + CFURLRef resources_url = CFBundleCopyResourcesDirectoryURL(bundle); + if (!resources_url) + goto fail_2; + + if (!CFEqual(bundle_url, resources_url)) + result = cfurlref_to_path(bundle_url); + + CFRelease(resources_url); +fail_2: + CFRelease(bundle_url); +fail_1: + return result; +} + +static gchar * +prepend_path_string(const gchar *prepended, const gchar *original) +{ + if (!prepended) + return g_strdup(original ? original : ""); + if (!original || !*original) + return g_strdup(prepended); + + GHashTable *seen = g_hash_table_new(g_str_hash, g_str_equal); + GPtrArray *unique = g_ptr_array_new(); + g_ptr_array_add(unique, (gpointer) prepended); + g_hash_table_add(seen, (gpointer) prepended); + + gchar **components = g_strsplit(original, ":", -1); + for (gchar **p = components; *p; p++) { + if (g_hash_table_contains(seen, *p)) + continue; + + g_ptr_array_add(unique, *p); + g_hash_table_add(seen, *p); + } + + g_ptr_array_add(unique, NULL); + gchar *result = g_strjoinv(":", (gchar **) unique->pdata); + g_hash_table_destroy(seen); + g_ptr_array_free(unique, TRUE); + + g_strfreev(components); + return result; +} + +// We reuse foreign dependencies, so we need to prevent them from loading +// any system-wide files, and point them in the right direction. +static void +adjust_environment(void) +{ + gchar *bundle_dir = get_application_bundle_path(); + if (!bundle_dir) + return; + + gchar *contents_dir = g_build_filename(bundle_dir, "Contents", NULL); + gchar *macos_dir = g_build_filename(contents_dir, "MacOS", NULL); + gchar *resources_dir = g_build_filename(contents_dir, "Resources", NULL); + gchar *datadir = g_build_filename(resources_dir, "share", NULL); + gchar *libdir = g_build_filename(resources_dir, "lib", NULL); + g_free(bundle_dir); + + gchar *new_path = prepend_path_string(macos_dir, g_getenv("PATH")); + g_setenv("PATH", new_path, TRUE); + g_free(new_path); + + const gchar *data_dirs = g_getenv("XDG_DATA_DIRS"); + gchar *new_data_dirs = data_dirs && *data_dirs + ? prepend_path_string(datadir, data_dirs) + : prepend_path_string(datadir, "/usr/local/share:/usr/share"); + g_setenv("XDG_DATA_DIRS", new_data_dirs, TRUE); + g_free(new_data_dirs); + + gchar *schemas_dir = g_build_filename(datadir, "glib-2.0", "schemas", NULL); + g_setenv("GSETTINGS_SCHEMA_DIR", schemas_dir, TRUE); + g_free(schemas_dir); + + gchar *gdk_pixbuf_module_file = + g_build_filename(libdir, "gdk-pixbuf-2.0", "loaders.cache", NULL); + g_setenv("GDK_PIXBUF_MODULE_FILE", gdk_pixbuf_module_file, TRUE); + g_free(gdk_pixbuf_module_file); + + // GTK+ is smart enough to also consider application bundles, + // but let there be a single source of truth. + g_setenv("GTK_EXE_PREFIX", resources_dir, TRUE); + + g_free(libdir); + g_free(datadir); + g_free(resources_dir); + g_free(macos_dir); + g_free(contents_dir); +} + +#endif + // --- Keyboard shortcuts ------------------------------------------------------ // Fuck XML, this can be easily represented in static structures. // Though it would be nice if the accelerators could be customized. @@ -667,7 +799,7 @@ enum { XX(S3, gtk_separator_new(GTK_ORIENTATION_HORIZONTAL)) \ XX(FIXATE, T("pin2-symbolic", "Keep zoom and position")) \ XX(MINUS, B("zoom-out-symbolic", "Zoom out")) \ - XX(SCALE, gtk_label_new("")) \ + XX(SCALE, B(NULL, "Set zoom level")) \ XX(PLUS, B("zoom-in-symbolic", "Zoom in")) \ XX(ONE, B("zoom-original-symbolic", "Original size")) \ XX(FIT, T("zoom-fit-best-symbolic", "Scale to fit")) \ @@ -1092,9 +1224,22 @@ 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)); +#ifdef __APPLE__ + // Otherwise we would always launch ourselves in the background. + gchar *bundle_dir = get_application_bundle_path(); + if (bundle_dir) { + g_ptr_array_add(a, g_strdup("open")); + g_ptr_array_add(a, g_strdup("-a")); + g_ptr_array_add(a, bundle_dir); + // At least with G_APPLICATION_NON_UNIQUE, this is necessary: + g_ptr_array_add(a, g_strdup("-n")); + g_ptr_array_add(a, g_strdup("--args")); + } +#endif + // Because we only pass URIs, there is no need to prepend "--" after this. + if (!a->len) + 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, @@ -1403,15 +1548,24 @@ on_window_state_event(G_GNUC_UNUSED GtkWidget *widget, static void show_help_contents(void) { - gchar *filename = g_strdup_printf("%s.html", PROJECT_NAME); #ifdef G_OS_WIN32 gchar *prefix = g_win32_get_package_installation_directory_of_module(NULL); - gchar *path = g_build_filename(prefix, PROJECT_DOCDIR, filename, NULL); - g_free(prefix); +#elif defined __APPLE__ + gchar *prefix = get_application_bundle_path(); + if (!prefix) { + show_error_dialog(g_error_new( + G_FILE_ERROR, G_FILE_ERROR_FAILED, "Cannot locate bundle")); + return; + } #else - gchar *path = g_build_filename(PROJECT_DOCDIR, filename, NULL); + gchar *prefix = g_strdup(PROJECT_PREFIX); #endif + + gchar *filename = g_strdup_printf("%s.html", PROJECT_NAME); + gchar *path = g_build_filename(prefix, PROJECT_DOCDIR, filename, NULL); + g_free(prefix); g_free(filename); + GError *error = NULL; gchar *uri = g_filename_to_uri(path, NULL, &error); g_free(path); @@ -1661,8 +1815,10 @@ static GtkWidget * make_toolbar_button(const char *symbolic, const char *tooltip) { GtkWidget *button = gtk_button_new(); - gtk_button_set_image(GTK_BUTTON(button), - gtk_image_new_from_icon_name(symbolic, GTK_ICON_SIZE_BUTTON)); + if (symbolic) { + gtk_button_set_image(GTK_BUTTON(button), + gtk_image_new_from_icon_name(symbolic, GTK_ICON_SIZE_BUTTON)); + } gtk_widget_set_tooltip_text(button, tooltip); gtk_widget_set_focus_on_click(button, FALSE); gtk_style_context_add_class( @@ -1808,7 +1964,8 @@ on_notify_view_scale( g_object_get(object, g_param_spec_get_name(param_spec), &scale, NULL); gchar *scale_str = g_strdup_printf("%.0f%%", round(scale * 100)); - gtk_label_set_text(GTK_LABEL(g.toolbar[TOOLBAR_SCALE]), scale_str); + gtk_label_set_text(GTK_LABEL( + gtk_bin_get_child(GTK_BIN(g.toolbar[TOOLBAR_SCALE]))), scale_str); g_free(scale_str); // FIXME: The label doesn't immediately assume its new width. @@ -1893,13 +2050,11 @@ make_view_toolbar(void) TOOLBAR(XX) #undef XX - gtk_widget_set_margin_start(g.toolbar[TOOLBAR_SCALE], 5); - gtk_widget_set_margin_end(g.toolbar[TOOLBAR_SCALE], 5); - + GtkWidget *scale_label = gtk_label_new(""); + gtk_container_add(GTK_CONTAINER(g.toolbar[TOOLBAR_SCALE]), scale_label); // So that the width doesn't jump around in the usual zoom range. // Ideally, we'd measure the widest digit and use width(NNN%). - gtk_label_set_width_chars(GTK_LABEL(g.toolbar[TOOLBAR_SCALE]), 5); - gtk_widget_set_halign(g.toolbar[TOOLBAR_SCALE], GTK_ALIGN_CENTER); + gtk_label_set_width_chars(GTK_LABEL(scale_label), 5); // GtkStatusBar solves a problem we do not have here. GtkWidget *view_toolbar = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0); @@ -1930,6 +2085,7 @@ make_view_toolbar(void) toolbar_command(TOOLBAR_PLAY_PAUSE, FIV_VIEW_COMMAND_TOGGLE_PLAYBACK); toolbar_command(TOOLBAR_SEEK_FORWARD, FIV_VIEW_COMMAND_FRAME_NEXT); toolbar_command(TOOLBAR_MINUS, FIV_VIEW_COMMAND_ZOOM_OUT); + toolbar_command(TOOLBAR_SCALE, FIV_VIEW_COMMAND_ZOOM_ASK); toolbar_command(TOOLBAR_PLUS, FIV_VIEW_COMMAND_ZOOM_IN); toolbar_command(TOOLBAR_ONE, FIV_VIEW_COMMAND_ZOOM_1); toolbar_toggler(TOOLBAR_FIT, "scale-to-fit"); @@ -2640,6 +2796,10 @@ main(int argc, char *argv[]) {}, }; +#ifdef __APPLE__ + adjust_environment(); +#endif + // We never get the ::open signal, thanks to G_OPTION_ARG_FILENAME_ARRAY. GtkApplication *app = gtk_application_new(NULL, G_APPLICATION_NON_UNIQUE); g_application_set_option_context_parameter_string( |
