Compare commits
14 Commits
v1.0.0
...
fd60959d41
| Author | SHA1 | Date | |
|---|---|---|---|
|
fd60959d41
|
|||
|
6b0bd4ac5c
|
|||
|
f543a46118
|
|||
|
9de3747328
|
|||
|
eb65d8582f
|
|||
|
c93a1fb9b7
|
|||
|
577de6bfb7
|
|||
|
690e60cd74
|
|||
|
a7ff9f220d
|
|||
|
2234fd008d
|
|||
|
0fceaf7728
|
|||
|
c46fc73c34
|
|||
|
bdd18fc898
|
|||
|
cf6ded1d03
|
1
.gitignore
vendored
1
.gitignore
vendored
@@ -1,3 +1,4 @@
|
||||
/.qtcreator
|
||||
/meson.build.user
|
||||
/subprojects/*
|
||||
!/subprojects/*.wrap
|
||||
|
||||
2
LICENSE
2
LICENSE
@@ -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.
|
||||
|
||||
15
README.adoc
15
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"]
|
||||
|
||||
@@ -69,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
|
||||
@@ -91,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],
|
||||
|
||||
@@ -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.
|
||||
@@ -1307,6 +1307,15 @@ fiv_browser_button_press_event(GtkWidget *widget, GdkEventButton *event)
|
||||
return GDK_EVENT_PROPAGATE;
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
@@ -1323,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;
|
||||
}
|
||||
@@ -1578,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:
|
||||
@@ -1635,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);
|
||||
}
|
||||
|
||||
@@ -380,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
|
||||
@@ -503,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_) {
|
||||
@@ -513,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);
|
||||
@@ -525,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);
|
||||
@@ -536,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),
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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);
|
||||
|
||||
125
fiv-view.c
125
fiv-view.c
@@ -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.
|
||||
@@ -1438,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)
|
||||
{
|
||||
@@ -2028,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") \
|
||||
|
||||
192
fiv.c
192
fiv.c
@@ -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);
|
||||
#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 *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);
|
||||
#else
|
||||
gchar *path = g_build_filename(PROJECT_DOCDIR, filename, NULL);
|
||||
#endif
|
||||
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(
|
||||
|
||||
52
macos-Info.plist.in
Normal file
52
macos-Info.plist.in
Normal file
@@ -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>
|
||||
25
macos-configure.sh
Executable file
25
macos-configure.sh
Executable file
@@ -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"
|
||||
115
macos-install.sh
Executable file
115
macos-install.sh
Executable file
@@ -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
|
||||
22
macos-svg2icns.sh
Executable file
22
macos-svg2icns.sh
Executable file
@@ -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"
|
||||
54
meson.build
54
meson.build
@@ -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,11 +391,7 @@ 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'
|
||||
|
||||
@@ -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
|
||||
|
||||
Submodule submodules/wuffs-mirror-release-c updated: c63c4a9348...50869df0ea
Reference in New Issue
Block a user