diff options
| -rw-r--r-- | .gitignore | 1 | ||||
| -rw-r--r-- | LICENSE | 2 | ||||
| -rw-r--r-- | README.adoc | 18 | ||||
| -rw-r--r-- | docs/fiv.html | 2 | ||||
| -rw-r--r-- | fiv-browser.c | 42 | ||||
| -rw-r--r-- | fiv-context-menu.c | 26 | ||||
| -rw-r--r-- | fiv-io-model.c | 5 | ||||
| -rw-r--r-- | fiv-io.c | 2 | ||||
| -rwxr-xr-x | fiv-reverse-search | 4 | ||||
| -rw-r--r-- | fiv-sidebar.c | 7 | ||||
| -rw-r--r-- | fiv-view.c | 133 | ||||
| -rw-r--r-- | fiv-view.h | 1 | ||||
| -rw-r--r-- | fiv.c | 192 | ||||
| -rw-r--r-- | macos-Info.plist.in | 52 | ||||
| -rwxr-xr-x | macos-configure.sh | 25 | ||||
| -rwxr-xr-x | macos-install.sh | 115 | ||||
| -rwxr-xr-x | macos-svg2icns.sh | 22 | ||||
| -rw-r--r-- | meson.build | 62 | ||||
| -rwxr-xr-x | msys2-configure.sh | 2 | ||||
| -rwxr-xr-x | msys2-package.sh | 3 | ||||
| m--------- | submodules/wuffs-mirror-release-c | 0 |
21 files changed, 663 insertions, 53 deletions
@@ -1,3 +1,4 @@ +/.qtcreator /meson.build.user /subprojects/* !/subprojects/*.wrap @@ -1,4 +1,4 @@ -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. diff --git a/README.adoc b/README.adoc index 9ca9e65..e9b734e 100644 --- a/README.adoc +++ b/README.adoc @@ -2,7 +2,7 @@ fiv === 'fiv' is a slightly unconventional, general-purpose image browser and viewer -for Linux and Windows (macOS still has major issues). +for Linux and Windows (macOS also kind of works). image::docs/fiv.webp["Screenshot of both the browser and the viewer"] @@ -38,6 +38,9 @@ You can get a package with the latest development version using Arch Linux's https://aur.archlinux.org/packages/fiv-git[AUR], or as a https://git.janouch.name/p/nixexprs[Nix derivation]. +https://janouch.name/cd[Windows installers can be found here], +you want the _x86_64_ version. + Building and Running -------------------- Build-only dependencies: @@ -66,6 +69,11 @@ you can get a quick and dirty installation package for testing purposes using: $ meson compile deb # dpkg -i fiv-*.deb +And in case you keep the default installation prefix rather than _/usr_, +it is necessary to: + + # glib-compile-schemas /usr/local/share/glib-2.0/schemas + Windows ~~~~~~~ 'fiv' can be cross-compiled for Windows, provided that you install a bunch of @@ -88,6 +96,14 @@ _mingw-w64-lcms2_ with the following change: sed -i 's/meson setup /&-Dfastfloat=true /' PKGCONFIG +macOS +~~~~~ +Support for this operating system isn't as good. +If you install Homebrew, you can get an application bundle with: + + $ sh -e macos-configure.sh builddir + $ meson install -C builddir + Documentation ------------- For information concerning usage, refer to link:docs/fiv.html[the user guide], diff --git a/docs/fiv.html b/docs/fiv.html index a458882..b4a5f33 100644 --- a/docs/fiv.html +++ b/docs/fiv.html @@ -16,7 +16,7 @@ q:lang(en):after { content: "’"; } <p class="details"> <span id="author">Přemysl Eric Janouch</span><br> <span id="email"><a href="mailto:p@janouch.name">p@janouch.name</a></span><br> -<span id="revnumber">version 0.0.0,</span> +<span id="revnumber">version 1.0.0,</span> <span id="revdate">2023-04-17</span> <p class="figure"><img src="fiv.webp" alt="fiv in browser and viewer modes"> diff --git a/fiv-browser.c b/fiv-browser.c index c9963f4..e1c9dfe 100644 --- a/fiv-browser.c +++ b/fiv-browser.c @@ -1,7 +1,7 @@ // // fiv-browser.c: filesystem browsing widget // -// 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. @@ -828,9 +828,18 @@ thumbnailer_next(Thumbnailer *t) "--thumbnail", fiv_thumbnail_sizes[self->item_size].thumbnail_spec_name, "--", uri, NULL}; + GSubprocessLauncher *launcher = + g_subprocess_launcher_new(G_SUBPROCESS_FLAGS_STDOUT_PIPE); +#ifdef G_OS_WIN32 + gchar *prefix = g_win32_get_package_installation_directory_of_module(NULL); + g_subprocess_launcher_set_cwd(launcher, prefix); + g_free(prefix); +#endif + GError *error = NULL; - t->minion = g_subprocess_newv(t->target->icon ? argv_faster : argv_slower, - G_SUBPROCESS_FLAGS_STDOUT_PIPE, &error); + t->minion = g_subprocess_launcher_spawnv( + launcher, t->target->icon ? argv_faster : argv_slower, &error); + g_object_unref(launcher); if (error) { g_warning("%s", error->message); g_error_free(error); @@ -1299,6 +1308,15 @@ fiv_browser_button_press_event(GtkWidget *widget, GdkEventButton *event) } static gboolean +modifier_state_opens_new_window(GtkWidget *widget, guint state) +{ + GdkModifierType primary = gdk_keymap_get_modifier_mask( + gdk_keymap_get_for_display(gtk_widget_get_display(widget)), + GDK_MODIFIER_INTENT_PRIMARY_ACCELERATOR); + return state == primary || state == GDK_SHIFT_MASK; +} + +static gboolean fiv_browser_button_release_event(GtkWidget *widget, GdkEventButton *event) { FivBrowser *self = FIV_BROWSER(widget); @@ -1314,11 +1332,13 @@ fiv_browser_button_release_event(GtkWidget *widget, GdkEventButton *event) if (!entry || entry != entry_at(self, event->x, event->y)) return GDK_EVENT_PROPAGATE; + guint state = event->state & gtk_accelerator_get_default_mod_mask(); if ((event->button == GDK_BUTTON_PRIMARY && state == 0)) return open_entry(widget, entry, FALSE); - if ((event->button == GDK_BUTTON_PRIMARY && state == GDK_CONTROL_MASK) || - (event->button == GDK_BUTTON_MIDDLE && state == 0)) + if ((event->button == GDK_BUTTON_MIDDLE && state == 0) || + (event->button == GDK_BUTTON_PRIMARY && + modifier_state_opens_new_window(widget, state))) return open_entry(widget, entry, TRUE); return GDK_EVENT_PROPAGATE; } @@ -1569,7 +1589,8 @@ static gboolean fiv_browser_key_press_event(GtkWidget *widget, GdkEventKey *event) { FivBrowser *self = FIV_BROWSER(widget); - switch ((event->state & gtk_accelerator_get_default_mod_mask())) { + guint state = event->state & gtk_accelerator_get_default_mod_mask(); + switch (state) { case 0: switch (event->keyval) { case GDK_KEY_Delete: @@ -1626,6 +1647,15 @@ fiv_browser_key_press_event(GtkWidget *widget, GdkEventKey *event) } } + if (modifier_state_opens_new_window(widget, state)) { + switch (event->keyval) { + case GDK_KEY_Return: + if (self->selected) + return open_entry(widget, self->selected, TRUE); + return GDK_EVENT_STOP; + } + } + return GTK_WIDGET_CLASS(fiv_browser_parent_class) ->key_press_event(widget, event); } diff --git a/fiv-context-menu.c b/fiv-context-menu.c index 16460b6..3284a2e 100644 --- a/fiv-context-menu.c +++ b/fiv-context-menu.c @@ -185,15 +185,24 @@ info_spawn(GtkWidget *dialog, const char *path, GBytes *bytes_in) if (bytes_in) flags |= G_SUBPROCESS_FLAGS_STDIN_PIPE; + GSubprocessLauncher *launcher = g_subprocess_launcher_new(flags); +#ifdef G_OS_WIN32 + // Both to find wperl, and then to let wperl find the nearby exiftool. + gchar *prefix = g_win32_get_package_installation_directory_of_module(NULL); + g_subprocess_launcher_set_cwd(launcher, prefix); + g_free(prefix); +#endif + // TODO(p): Add a fallback to internal capabilities. // The simplest is to specify the filename and the resolution. GError *error = NULL; - GSubprocess *subprocess = g_subprocess_new(flags, &error, + GSubprocess *subprocess = g_subprocess_launcher_spawn(launcher, &error, #ifdef G_OS_WIN32 "wperl", #endif "exiftool", "-tab", "-groupNames", "-duplicates", "-extractEmbedded", "--binary", "-quiet", "--", path, NULL); + g_object_unref(launcher); if (error) { info_redirect_error(dialog, error); return; @@ -371,8 +380,11 @@ append_opener(GtkWidget *menu, GAppInfo *opener, const OpenContext *template) ctx->app_info = opener; // On Linux, this prefers the obsoleted X-GNOME-FullName. - gchar *name = - g_strdup_printf("Open With %s", g_app_info_get_display_name(opener)); + const char *display_name = g_app_info_get_display_name(opener); + // Ironically, GIO reads CFBundleName and can't read CFBundleDisplayName. + if (!display_name) + display_name = g_app_info_get_executable(opener); + gchar *name = g_strdup_printf("Open With %s", display_name); // It's documented that we can touch the child, if we want to use markup. #if 0 @@ -494,8 +506,6 @@ fiv_context_menu_new(GtkWidget *widget, GFile *file) GAppInfo *default_ = g_app_info_get_default_for_type(ctx->content_type, FALSE); - GList *recommended = g_app_info_get_recommended_for_type(ctx->content_type); - GList *fallback = g_app_info_get_fallback_for_type(ctx->content_type); GtkWidget *menu = gtk_menu_new(); if (default_) { @@ -504,6 +514,7 @@ fiv_context_menu_new(GtkWidget *widget, GFile *file) GTK_MENU_SHELL(menu), gtk_separator_menu_item_new()); } + GList *recommended = g_app_info_get_recommended_for_type(ctx->content_type); for (GList *iter = recommended; iter; iter = iter->next) { if (!default_ || !g_app_info_equal(iter->data, default_)) append_opener(menu, iter->data, ctx); @@ -516,6 +527,10 @@ fiv_context_menu_new(GtkWidget *widget, GFile *file) GTK_MENU_SHELL(menu), gtk_separator_menu_item_new()); } + // The implementation returns the same data for both, + // we'd have to filter out the recommended ones from here. +#ifndef __APPLE__ + GList *fallback = g_app_info_get_fallback_for_type(ctx->content_type); for (GList *iter = fallback; iter; iter = iter->next) { if (!default_ || !g_app_info_equal(iter->data, default_)) append_opener(menu, iter->data, ctx); @@ -527,6 +542,7 @@ fiv_context_menu_new(GtkWidget *widget, GFile *file) gtk_menu_shell_append( GTK_MENU_SHELL(menu), gtk_separator_menu_item_new()); } +#endif GtkWidget *item = gtk_menu_item_new_with_label("Open With..."); g_signal_connect_data(item, "activate", G_CALLBACK(on_chooser_activate), diff --git a/fiv-io-model.c b/fiv-io-model.c index 3309702..6da3e3c 100644 --- a/fiv-io-model.c +++ b/fiv-io-model.c @@ -382,6 +382,9 @@ on_monitor_changed(G_GNUC_UNUSED GFileMonitor *monitor, GFile *file, switch (event_type) { case G_FILE_MONITOR_EVENT_CHANGED: case G_FILE_MONITOR_EVENT_ATTRIBUTE_CHANGED: + // On macOS, we seem to not receive _CHANGED for child files. + // And while this seems to arrive too early, it's a mild improvement. + case G_FILE_MONITOR_EVENT_CHANGES_DONE_HINT: event = MONITOR_CHANGING; new_entry_file = file; break; @@ -400,8 +403,6 @@ on_monitor_changed(G_GNUC_UNUSED GFileMonitor *monitor, GFile *file, new_entry_file = file; break; - case G_FILE_MONITOR_EVENT_CHANGES_DONE_HINT: - // TODO(p): Figure out if we can't make use of _CHANGES_DONE_HINT. case G_FILE_MONITOR_EVENT_PRE_UNMOUNT: case G_FILE_MONITOR_EVENT_UNMOUNTED: // TODO(p): Figure out how to handle _UNMOUNTED sensibly. @@ -976,7 +976,7 @@ static uint32_t * parse_mpf_index_entries(const struct tiffer *T, struct tiffer_entry *entry) { uint32_t count = entry->remaining_count / 16; - uint32_t *offsets = g_malloc0_n(sizeof *offsets, count + 1), *out = offsets; + uint32_t *offsets = g_malloc0_n(count + 1, sizeof *offsets), *out = offsets; for (uint32_t i = 0; i < count; i++) { // 5.2.3.3.3. Individual Image Data Offset uint32_t offset = parse_mpf_mpentry(entry->p + i * 16, T); diff --git a/fiv-reverse-search b/fiv-reverse-search index 5210703..dc12790 100755 --- a/fiv-reverse-search +++ b/fiv-reverse-search @@ -5,5 +5,5 @@ if [ "$#" -ne 2 ]; then fi xdg-open "$1$(fiv --thumbnail-for-search large "$2" \ - | curl --silent --show-error --upload-file - https://transfer.sh/image \ - | jq --slurp --raw-input --raw-output @uri)" + | curl --silent --show-error --form 'files[]=@-' https://uguu.se/upload \ + | jq --raw-output '.files[] | .url | @uri')" diff --git a/fiv-sidebar.c b/fiv-sidebar.c index 900c8a8..5445fd0 100644 --- a/fiv-sidebar.c +++ b/fiv-sidebar.c @@ -537,6 +537,13 @@ on_show_enter_location( g_signal_connect(entry, "changed", G_CALLBACK(on_enter_location_changed), self); + GFile *location = fiv_io_model_get_location(self->model); + if (location) { + gchar *parse_name = g_file_get_parse_name(location); + gtk_entry_set_text(GTK_ENTRY(entry), parse_name); + g_free(parse_name); + } + // Can't have it ellipsized and word-wrapped at the same time. GtkWidget *protocols = gtk_label_new(""); gtk_label_set_ellipsize(GTK_LABEL(protocols), PANGO_ELLIPSIZE_END); @@ -1,7 +1,7 @@ // // fiv-view.c: image viewing widget // -// 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. @@ -851,6 +851,10 @@ gl_draw(FivView *self, cairo_t *cr) cliph = allocation.height; } + int scale = gtk_widget_get_scale_factor(GTK_WIDGET(self)); + clipw *= scale; + cliph *= scale; + enum { SRC, DEST }; GLuint textures[2] = {}; glGenTextures(2, textures); @@ -958,12 +962,14 @@ gl_draw(FivView *self, cairo_t *cr) // XXX: Native GdkWindows send this to the software fallback path. // XXX: This only reliably alpha blends when using the software fallback, // such as with a native window, because 7237f5d in GTK+ 3 is a regression. + // (Introduced in 3.24.39, reverted in 3.24.42.) + // // We had to resort to rendering the checkerboard pattern in the shader. // Unfortunately, it is hard to retrieve the theme colours from CSS. GdkWindow *window = gtk_widget_get_window(GTK_WIDGET(self)); cairo_translate(cr, dx, dy); gdk_cairo_draw_from_gl( - cr, window, textures[DEST], GL_TEXTURE, 1, 0, 0, clipw, cliph); + cr, window, textures[DEST], GL_TEXTURE, scale, 0, 0, clipw, cliph); gdk_gl_context_make_current(self->gl_context); glDeleteBuffers(1, &vertex_buffer); @@ -1432,6 +1438,127 @@ get_toplevel(GtkWidget *widget) return NULL; } +struct zoom_ask_context { + GtkWidget *result_left, *result_right; + Dimensions dimensions; +}; + +static void +on_zoom_ask_spin_changed(GtkSpinButton *spin, G_GNUC_UNUSED gpointer user_data) +{ + // We don't want to call gtk_spin_button_update(), + // that would immediately replace whatever the user has typed in. + gdouble scale = -1; + const gchar *text = gtk_entry_get_text(GTK_ENTRY(spin)); + if (*text) { + gchar *end = NULL; + gdouble value = g_strtod(text, &end); + if (!*end) + scale = value / 100.; + } + + struct zoom_ask_context *data = user_data; + GtkStyleContext *style = gtk_widget_get_style_context(GTK_WIDGET(spin)); + if (scale <= 0) { + gtk_style_context_add_class(style, GTK_STYLE_CLASS_WARNING); + gtk_label_set_text(GTK_LABEL(data->result_left), "—"); + gtk_label_set_text(GTK_LABEL(data->result_right), "—"); + } else { + gtk_style_context_remove_class(style, GTK_STYLE_CLASS_WARNING); + gchar *left = g_strdup_printf("%.0f", data->dimensions.width * scale); + gchar *right = g_strdup_printf("%.0f", data->dimensions.height * scale); + gtk_label_set_text(GTK_LABEL(data->result_left), left); + gtk_label_set_text(GTK_LABEL(data->result_right), right); + g_free(left); + g_free(right); + } +} + +static void +zoom_ask(FivView *self) +{ + GtkWidget *dialog = gtk_dialog_new_with_buttons("Set zoom level", + get_toplevel(GTK_WIDGET(self)), + GTK_DIALOG_DESTROY_WITH_PARENT | GTK_DIALOG_MODAL | + GTK_DIALOG_USE_HEADER_BAR, + "_OK", GTK_RESPONSE_ACCEPT, "_Cancel", GTK_RESPONSE_CANCEL, NULL); + + Dimensions dimensions = get_surface_dimensions(self); + gchar *original_width = g_strdup_printf("%.0f", dimensions.width); + gchar *original_height = g_strdup_printf("%.0f", dimensions.height); + GtkWidget *original_left = gtk_label_new(original_width); + GtkWidget *original_middle = gtk_label_new("×"); + GtkWidget *original_right = gtk_label_new(original_height); + g_free(original_width); + g_free(original_height); + gtk_label_set_xalign(GTK_LABEL(original_left), 1.); + gtk_label_set_xalign(GTK_LABEL(original_right), 0.); + + GtkWidget *original_box = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 6); + gtk_box_pack_start( + GTK_BOX(original_box), original_left, TRUE, TRUE, 0); + gtk_box_pack_start( + GTK_BOX(original_box), original_middle, FALSE, FALSE, 0); + gtk_box_pack_start( + GTK_BOX(original_box), original_right, TRUE, TRUE, 0); + + // FIXME: This widget's behaviour is absolutely miserable. + // For example, we would like to be flexible with decimal spaces. + GtkAdjustment *adjustment = gtk_adjustment_new( + self->scale * 100, 0., 100000., 1., 10., 0.); + GtkWidget *spin = gtk_spin_button_new(adjustment, 1., 2); + gtk_spin_button_set_update_policy( + GTK_SPIN_BUTTON(spin), GTK_UPDATE_IF_VALID); + gtk_entry_set_activates_default(GTK_ENTRY(spin), TRUE); + + GtkWidget *zoom_box = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 6); + GtkWidget *zoom_label = gtk_label_new_with_mnemonic("_Zoom:"); + gtk_label_set_mnemonic_widget(GTK_LABEL(zoom_label), spin); + gtk_box_pack_start(GTK_BOX(zoom_box), zoom_label, FALSE, FALSE, 0); + gtk_box_pack_start(GTK_BOX(zoom_box), spin, TRUE, TRUE, 0); + gtk_box_pack_start(GTK_BOX(zoom_box), gtk_label_new("%"), FALSE, FALSE, 0); + + GtkWidget *result_left = gtk_label_new(NULL); + GtkWidget *result_middle = gtk_label_new("×"); + GtkWidget *result_right = gtk_label_new(NULL); + gtk_label_set_xalign(GTK_LABEL(result_left), 1.); + gtk_label_set_xalign(GTK_LABEL(result_right), 0.); + + GtkSizeGroup *group = gtk_size_group_new(GTK_SIZE_GROUP_HORIZONTAL); + gtk_size_group_add_widget(group, original_left); + gtk_size_group_add_widget(group, original_right); + gtk_size_group_add_widget(group, result_left); + gtk_size_group_add_widget(group, result_right); + + GtkWidget *result_box = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 6); + gtk_box_pack_start(GTK_BOX(result_box), result_left, TRUE, TRUE, 0); + gtk_box_pack_start(GTK_BOX(result_box), result_middle, FALSE, FALSE, 0); + gtk_box_pack_start(GTK_BOX(result_box), result_right, TRUE, TRUE, 0); + + struct zoom_ask_context data = { result_left, result_right, dimensions }; + g_signal_connect(spin, "changed", + G_CALLBACK(on_zoom_ask_spin_changed), &data); + on_zoom_ask_spin_changed(GTK_SPIN_BUTTON(spin), &data); + + GtkWidget *content = gtk_dialog_get_content_area(GTK_DIALOG(dialog)); + g_object_set(content, "margin", 12, NULL); + gtk_box_set_spacing(GTK_BOX(content), 6); + gtk_container_add(GTK_CONTAINER(content), original_box); + gtk_container_add(GTK_CONTAINER(content), zoom_box); + gtk_container_add(GTK_CONTAINER(content), result_box); + gtk_dialog_set_default_response(GTK_DIALOG(dialog), GTK_RESPONSE_ACCEPT); + gtk_window_set_skip_taskbar_hint(GTK_WINDOW(dialog), TRUE); + gtk_widget_show_all(dialog); + + if (gtk_dialog_run(GTK_DIALOG(dialog)) == GTK_RESPONSE_ACCEPT) { + double value = gtk_spin_button_get_value(GTK_SPIN_BUTTON(spin)); + if (value > 0) + set_scale(self, value / 100., NULL); + } + gtk_widget_destroy(dialog); + g_object_unref(group); +} + static void copy(FivView *self) { @@ -2022,6 +2149,8 @@ fiv_view_command(FivView *self, FivViewCommand command) set_scale(self, self->scale / SCALE_STEP, NULL); break; case FIV_VIEW_COMMAND_ZOOM_1: set_scale(self, 1.0, NULL); + break; case FIV_VIEW_COMMAND_ZOOM_ASK: + zoom_ask(self); break; case FIV_VIEW_COMMAND_FIT_WIDTH: set_scale_to_fit_width(self); break; case FIV_VIEW_COMMAND_FIT_HEIGHT: @@ -59,6 +59,7 @@ typedef enum _FivViewCommand { XX(FIV_VIEW_COMMAND_ZOOM_IN, "zoom-in") \ XX(FIV_VIEW_COMMAND_ZOOM_OUT, "zoom-out") \ XX(FIV_VIEW_COMMAND_ZOOM_1, "zoom-1") \ + XX(FIV_VIEW_COMMAND_ZOOM_ASK, "zoom-ask") \ XX(FIV_VIEW_COMMAND_FIT_WIDTH, "fit-width") \ XX(FIV_VIEW_COMMAND_FIT_HEIGHT, "fit-height") \ XX(FIV_VIEW_COMMAND_TOGGLE_SCALE_TO_FIT, "toggle-scale-to-fit") \ @@ -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( diff --git a/macos-Info.plist.in b/macos-Info.plist.in new file mode 100644 index 0000000..b518a62 --- /dev/null +++ b/macos-Info.plist.in @@ -0,0 +1,52 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> +<plist version="1.0"> + <dict> + <key>CFBundleExecutable</key> + <string>@ProjectName@</string> + <key>CFBundleIdentifier</key> + <string>@ProjectNS@@ProjectName@</string> + <key>CFBundleName</key> + <string>@ProjectName@</string> + <key>CFBundleIconFile</key> + <string>@ProjectName@.icns</string> + <key>CFBundleShortVersionString</key> + <string>@ProjectVersion@</string> + <key>CFBundleInfoDictionaryVersion</key> + <string>6.0</string> + <key>CFBundlePackageType</key> + <string>APPL</string> + + <!-- Although mostly static, this should eventually be generated. --> + <!-- In particular, we should expand image/x-dcraw, --> + <!-- using information we can collect from shared-mime-info. --> + <key>CFBundleDocumentTypes</key> + <array> + <dict> + <key>CFBundleTypeName</key> + <string>Image File</string> + <key>CFBundleTypeRole</key> + <string>Viewer</string> + <key>LSHandlerRank</key> + <string>Default</string> + <key>LSItemContentTypes</key> + <array> + <string>com.apple.icns</string> + <string>com.apple.quicktime-image</string> + <string>com.compuserve.gif</string> + <string>com.microsoft.bmp</string> + <string>com.microsoft.ico</string> + <string>org.webmproject.webp</string> + <string>public.avif</string> + <string>public.heic</string> + <string>public.heif</string> + <string>public.jpeg</string> + <string>public.png</string> + <string>public.svg-image</string> + <string>public.tiff</string> + <string>public.xbitmap-image</string> + </array> + </dict> + </array> + </dict> +</plist> diff --git a/macos-configure.sh b/macos-configure.sh new file mode 100755 index 0000000..b7a87db --- /dev/null +++ b/macos-configure.sh @@ -0,0 +1,25 @@ +#!/bin/sh -e +# macos-configure.sh: set up a Homebrew-based macOS build +# +# Meson has no special support for macOS application bundles whatsoever. +# +# gtk-mac-bundler doesn't do anything particularly miraculous, +# and it doesn't directly support Homebrew. +# +# It would be cleaner and more reproducible to set up a special HOMEBREW_PREFIX, +# though right now we're happy to build an app bundle at all. +# +# It would also allow us to make a custom Little CMS build that includes +# the fast float plugin, which is a bit of a big deal. + +# TODO: exiftool (Perl is part of macOS, at least for now) +HOMEBREW_NO_AUTO_UPDATE=1 HOMEBREW_ASK=1 brew install + coreutils meson pkgconf shared-mime-info adwaita-icon-theme \ + gtk+3 jpeg-xl libavif libheif libraw librsvg little-cms2 webp + +sourcedir=$(grealpath "${2:-$(dirname "$0")}") +builddir=$(grealpath "${1:-builddir}") +appdir=$builddir/fiv.app +meson setup --buildtype=debugoptimized --prefix="$appdir" \ + --bindir=Contents/MacOS --libdir=Contents/Resources/lib \ + --datadir=Contents/Resources/share "$builddir" "$sourcedir" diff --git a/macos-install.sh b/macos-install.sh new file mode 100755 index 0000000..884e1b1 --- /dev/null +++ b/macos-install.sh @@ -0,0 +1,115 @@ +#!/bin/sh -e +export LC_ALL=C +cd "$MESON_INSTALL_DESTDIR_PREFIX" + +# Input: Half-baked application bundle linked against Homebrew. +# Output: Portable application bundle. +source=/opt/homebrew +bindir=Contents/MacOS +libdir=Contents/Resources/lib +datadir=Contents/Resources/share + +mkdir -p "$datadir"/glib-2.0/schemas +cp -p "$source"/share/glib-2.0/schemas/org.gtk.Settings.* \ + "$datadir"/glib-2.0/schemas +mkdir -p "$datadir"/icons +cp -pRL "$source"/share/icons/Adwaita "$datadir/"icons +mkdir -p "$datadir"/icons/hicolor +cp -p "$source"/share/icons/hicolor/index.theme "$datadir"/icons/hicolor +mkdir -p "$datadir/mime" +# GIO doesn't use the database on macOS, this subset is for us. +find "$source"/share/mime/ -maxdepth 1 -type f -exec cp -p {} "$datadir"/mime \; + +# Copy binaries we directly or indirectly depend on. +# +# Homebrew is a bit chaotic in that some libraries are linked against locations +# in /opt/homebrew/Cellar, and some against /opt/homebrew/opt symlinks. +# We'll process things in such a way that it does not matter. +# +# As a side note, libraries in /usr/lib are now actually being served from +# a shared cache by the dynamic linker and aren't visible on the filesystem. +# There is an alternative to "otool -L" which can see them but it isn't +# particularly nicer to parse: "dyld_info -dependents/-linked_dylibs". +rm -rf "$libdir" +mkdir -p "$libdir" + +pixbufdir=$libdir/gdk-pixbuf-2.0 +loadersdir=$pixbufdir/loaders +cp -RL "$source"/lib/gdk-pixbuf-2.0/* "$pixbufdir" + +# Fix a piece of crap loader that needs to be special. +svg=$loadersdir/libpixbufloader_svg.so +rm -f "$loadersdir"/libpixbufloader_svg.dylib +otool -L "$svg" | grep -o '@rpath/[^ ]*' | while IFS= read -r bad +do install_name_tool -change "$bad" "$source/lib/$(basename "$bad")" "$svg" +done + +GDK_PIXBUF_MODULEDIR=$loadersdir gdk-pixbuf-query-loaders \ + | sed "s,$libdir,@rpath," > "$pixbufdir/loaders.cache" + +gtkdir=$libdir/gtk-3.0 +printbackendsdir=$gtkdir/printbackends +cp -RL "$source"/lib/gtk-3.0/* "$gtkdir" + +# TODO: Figure out how to make gtk-query-immodules-3.0 pick up exactly +# what it needs to. So far I'm not sure if this is at all even useful. +rm -rf "$gtkdir"/immodules* + +find "$bindir" "$loadersdir" "$printbackendsdir" -type f -maxdepth 1 | awk ' + function collect(binary, command, line) { + if (seen[binary]++) + return + + command = "otool -L \"" binary "\"" + while ((command | getline line) > 0) + if (match(line, /^\t\/opt\/.+ \(/)) + collect(substr(line, RSTART + 1, RLENGTH - 3)) + close(command) + } { + collect($0) + delete seen[$0] + } END { + for (library in seen) + print library + } +' | while IFS= read -r binary +do test -f "$libdir/$(basename "$binary")" || cp "$binary" "$libdir" +done + +# Now redirect all binaries to internal linking. +# A good overview of how this works is "man dyld" and: +# https://itwenty.me/posts/01-understanding-rpath/ +rewrite() { + otool -L "$1" | sed -n 's,^\t\(.*\) (.*,\1,p' | grep '^/opt/' \ + | while IFS= read -r lib + do install_name_tool -change "$lib" "@rpath/$(basename "$lib")" "$1" + done +} + +find "$bindir" -type f -maxdepth 1 | while IFS= read -r binary +do + install_name_tool -add_rpath @executable_path/../Resources/lib "$binary" + rewrite "$binary" +done + +find "$libdir" -type f \( -name '*.so' -o -name '*.dylib' \) \ + | while IFS= read -r binary +do + chmod 644 "$binary" + install_name_tool -id "@rpath/${binary#$libdir/}" "$binary" + rewrite "$binary" + + # Discard pointless @loader_path/../lib and absolute Homebrew paths. + otool -l "$binary" | awk ' + $1 == "cmd" { command = $2 } + command == "LC_RPATH" && $1 == "path" { print $2 } + ' | xargs -R 1 -I % install_name_tool -delete_rpath % "$binary" + + # Replace freshly invalidated code signatures with ad-hoc ones. + codesign --force --sign - "$binary" +done + +glib-compile-schemas "$datadir"/glib-2.0/schemas + +# This may speed up program start-up a little bit. +gtk-update-icon-cache "$datadir"/icons/Adwaita diff --git a/macos-svg2icns.sh b/macos-svg2icns.sh new file mode 100755 index 0000000..791e37e --- /dev/null +++ b/macos-svg2icns.sh @@ -0,0 +1,22 @@ +#!/bin/sh -e +# macos-svg2icns.sh: convert an SVG to the macOS .icns format +if [ $# -ne 2 ] +then + echo >&2 "Usage: $0 INPUT.svg OUTPUT.icns" + exit 2 +fi + +svg=$1 icns=$2 tmpdir=$(mktemp -d) +trap 'rm -rf "$tmpdir"' EXIT + +iconset="$tmpdir/$(basename "$icns" .icns).iconset" +mkdir -p "$iconset" +for size in 16 32 128 256 512 +do + size2x=$((size * 2)) + rsvg-convert --output="$iconset/icon_${size}x${size}.png" \ + --width=$size --height=$size "$svg" + rsvg-convert --output="$iconset/icon_${size}x${size}@2x.png" \ + --width=$size2x --height=$size2x "$svg" +done +iconutil -c icns -o "$icns" "$iconset" diff --git a/meson.build b/meson.build index 82e7978..8f879ba 100644 --- a/meson.build +++ b/meson.build @@ -1,7 +1,7 @@ # vim: noet ts=4 sts=4 sw=4: project('fiv', 'c', default_options : ['c_std=gnu99', 'warning_level=2'], - version : '0.1.0', + version : '1.0.0', meson_version : '>=0.57') cc = meson.get_compiler('c') @@ -18,6 +18,8 @@ add_project_arguments( #endif win32 = host_machine.system() == 'windows' +macos = host_machine.system() == 'darwin' \ + and host_machine.subsystem() == 'macos' # The likelihood of this being installed is nearly zero. Enable the wrap. libjpegqs = dependency('libjpegqs', required : get_option('libjpegqs'), @@ -97,15 +99,20 @@ docdir = get_option('datadir') / 'doc' / meson.project_name() application_ns = 'name.janouch.' application_url = 'https://janouch.name/p/' + meson.project_name() +rawconf = configuration_data({ + 'ProjectName' : meson.project_name(), + 'ProjectVersion' : meson.project_version(), + 'ProjectNS' : application_ns, + 'ProjectURL' : application_url, +}) + conf = configuration_data() conf.set_quoted('PROJECT_NAME', meson.project_name()) conf.set_quoted('PROJECT_VERSION', '@VCS_TAG@') conf.set_quoted('PROJECT_NS', application_ns) conf.set_quoted('PROJECT_URL', application_url) -conf.set_quoted('PROJECT_DOCDIR', get_option('prefix') / docdir) -if win32 - conf.set_quoted('PROJECT_DOCDIR', docdir) -endif +conf.set_quoted('PROJECT_PREFIX', get_option('prefix')) +conf.set_quoted('PROJECT_DOCDIR', docdir) conf.set('HAVE_JPEG_QS', libjpegqs.found()) conf.set('HAVE_LCMS2', lcms2.found()) @@ -147,6 +154,15 @@ if win32 output : 'fiv.ico', input : icon_png_list, command : [icotool, '-c', '-o', '@OUTPUT@', '@INPUT@']) rc += windows.compile_resources('fiv.rc', depends : icon_ico) +elif macos + # Meson is really extremely brain-dead and retarded. + # There is no real reason why this would have to be a shell script. + svg2icns = find_program('macos-svg2icns.sh') + icon_icns = custom_target('fiv.icns', + output : 'fiv.icns', input : 'fiv.svg', + command : [svg2icns, '@INPUT@', '@OUTPUT@'], + install : true, + install_dir : 'Contents/Resources') endif gnome = import('gnome') @@ -214,13 +230,12 @@ foreach schema : gsettings_schemas input : schema, output : application_ns + schema, copy : true, - install: true, + install : true, install_dir : get_option('datadir') / 'glib-2.0' / 'schemas') endforeach # For the purposes of development: make the program find its GSettings schemas. gnome.compile_schemas(depend_files : files(gsettings_schemas)) -gnome.post_install(glib_compile_schemas : true, gtk_update_icon_cache : true) # Meson is broken on Windows and removes the backslashes, so this ends up empty. symbolics = run_command(find_program('sed', required : false, disabler : true), @@ -256,7 +271,26 @@ install_data('fiv.svg', install_subdir('docs', install_dir : docdir, strip_directory : true) -if not win32 +if macos + # We're going all in on application bundles, seeing as it doesn't make + # much sense to install the application as in the block below. + # + # macOS has other mechanisms we can use to launch the JPEG cropper, + # or the reverse search utilities. + configure_file( + input : 'macos-Info.plist.in', + output : 'Info.plist', + configuration : rawconf, + install : true, + install_dir : 'Contents') + + meson.add_install_script('macos-install.sh') +elif not win32 + gnome.post_install( + glib_compile_schemas : true, + gtk_update_icon_cache : true, + ) + asciidoctor = find_program('asciidoctor', required : false) a2x = find_program('a2x', required : false) if not asciidoctor.found() and not a2x.found() @@ -357,16 +391,14 @@ elif meson.is_cross_build() wxs = configure_file( input : 'fiv.wxs.in', output : 'fiv.wxs', - configuration : configuration_data({ - 'ProjectName' : meson.project_name(), - 'ProjectVersion' : meson.project_version(), - 'ProjectURL' : application_url, - }), + configuration : rawconf, ) + msi = meson.project_name() + '-' + meson.project_version() + \ + '-' + host_machine.cpu() + '.msi' custom_target('package', - output : 'fiv.msi', + output : msi, command : [meson.current_source_dir() / 'msys2-package.sh', - host_machine.cpu(), 'fiv.msi', wxs], + host_machine.cpu(), msi, wxs], env : ['MESON_BUILD_ROOT=' + meson.current_build_dir(), 'MESON_SOURCE_ROOT=' + meson.current_source_dir()], console : true, diff --git a/msys2-configure.sh b/msys2-configure.sh index 7b7724e..8cbff30 100755 --- a/msys2-configure.sh +++ b/msys2-configure.sh @@ -130,6 +130,8 @@ setup() { --bindir . --libdir . --cross-file="$toolchain" "$builddir" "$sourcedir" } +# Note: you may need GNU coreutils realpath for non-existent build directories +# (macOS and busybox will probably not work). sourcedir=$(realpath "${2:-$(dirname "$0")}") builddir=$(realpath "${1:-builddir}") toolchain=$builddir/msys2-cross-toolchain.meson diff --git a/msys2-package.sh b/msys2-package.sh index 27729bb..b929cbc 100755 --- a/msys2-package.sh +++ b/msys2-package.sh @@ -1,7 +1,8 @@ #!/bin/sh -e export LC_ALL=C cd "$MESON_BUILD_ROOT" -arch=$1 msi=$2 files=package-files.wxs destdir=$(pwd)/package +arch=$1 msi=$2 files=package-files.wxs +destdir=$(pwd)/package/${msi%.*} shift 2 # We're being passed host_machine.cpu(), which will be either x86 or x86_64. diff --git a/submodules/wuffs-mirror-release-c b/submodules/wuffs-mirror-release-c -Subproject c63c4a9348fb1b52a9b60a6eb62328a97d979d9 +Subproject 50869df0ea703b4f41b238bfe26aec6ec9c8688 |
