CodeGrinder
CodeGrinder

Reputation: 201

How to open JPG image in application written in C on MacOS

I want to write a simple image browser in C and then make a bundle for the MacOs.

The problem is that I am encountering a warning when I am trying to open a jpg file from a context menu in Finder.

enter image description here

I compiled my code, I prepared a iViewGtk.app bundle with Info.plist file.

The Info.plist is as follows:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
    <dict>
        <key>CFBundleExecutable</key>
        <string>iViewGtk</string>
        <key>CFBundleName</key>
        <string>iViewGtk</string>
        <key>CFBundleGetInfoString</key>
        <string>5.0</string>
        <key>CFBundleTypeName</key>
        <string>3r23rfewfwefwef343f</string>
        <key>CFBundleTypeOSTypes</key>
        <array>
            <string>****</string>
        </array>
        <key>CFBundleTypeMIMETypes</key>
        <string>image/jpeg</string>
        <key>CFBundleIconFile</key>
        <string>icon.icns</string>
        <key>CFBundleIdentifier</key>
        <string>net.bean.gtk.iview</string>
        <key>CFBundlePackageType</key>
        <string>APPL</string>
        <key>LSMinimumSystemVersion</key>
        <string>10.14</string>
        <key>NSPrincipalClass</key>
        <string>NSApplication</string>
        <key>CFBundleShortVersionString</key>
        <string>5.0</string>
        <key>CFBundleDocumentTypes</key>
        <array>
            <dict>
                <key>CFBundleTypeExtensions</key>
                <array>
                    <string>bmp</string>
                    <string>cur</string>
                    <string>gif</string>
                    <string>icns</string>
                    <string>ico</string>
                    <string>jpeg</string>
                    <string>jpg</string>
                    <string>jpe</string>
                    <string>jfi</string>
                    <string>jfif</string>
                    <string>pbm</string>
                    <string>pgm</string>
                    <string>png</string>
                    <string>ppm</string>
                    <string>svg</string>
                    <string>svgz</string>
                    <string>tif</string>
                    <string>tiff</string>
                    <string>wbmp</string>
                    <string>webp</string>
                    <string>xbm</string>
                    <string>xpm</string>
                    <string>ani</string>
                    <string>apng</string>
                    <string>avif</string>
                    <string>avifs</string>
                    <string>bw</string>
                    <string>exr</string>
                    <string>hdr</string>
                    <string>heic</string>
                    <string>heif</string>
                    <string>jxl</string>
                    <string>kra</string>
                    <string>ora</string>
                    <string>pcx</string>
                    <string>pic</string>
                    <string>psd</string>
                    <string>ras</string>
                    <string>rgb</string>
                    <string>rgba</string>
                    <string>sgi</string>
                    <string>tga</string>
                    <string>xcf</string>
                </array>
                <key>CFBundleTypeRole</key>
                <string>Viewer</string>
            </dict>
        </array>
        <key>NSSupportsAutomaticGraphicsSwitching</key>
        <true />
        <key>NSHighResolutionCapable</key>
        <true />
    </dict>
</plist>

Actually the Info.plist file is a copy of Info.plist file from a working application qView which can be installed using brew install qview.

In addition to that I am able to click on the bundle and open the application by clicking it.

I copied the app into Application folder and it seems that Finder recognizes it as application which can open image files. It is not grayed out:

enter image description here

Since I don't know how the parameter from Finder is provided to my app, I didn't implement the part which opens and displays the image. I think, that it shouldn't be a problem for MacOS, should be? The main.c looks as follows:

int main(int argc, char **argv)
{
    struct ApplicationContext *appContext = malloc(sizeof(appContext));
    GtkApplication *app;
    int status;

    app = gtk_application_new("net.bean.app", G_APPLICATION_HANDLES_OPEN);
    g_signal_connect(app, "activate", G_CALLBACK(activate), appContext);
    g_signal_connect(app, "open", G_CALLBACK(app_open), appContext);
    status = g_application_run(G_APPLICATION(app), argc, argv);
    g_object_unref(app);

    return status;
}

Could someone tell me why MacOS displays the warning?

Is this somehow related to some MacOs security policies which are forbidding to open a file to an unknown app?

Thank you.

Upvotes: 0

Views: 185

Answers (1)

Stephan Schlecht
Stephan Schlecht

Reputation: 27106

It's not sufficient to have a .plist file, there is also handling code necessary.

By the way, this is also the case for a regular Cocoa Objective-C applications. If none of the corresponding optional NSApplicationDelegate methods (such as application:openURLs: and friends) is implemented there, the dialog shown in the question appears.

Neither from the sample code provided nor from the tags used can one conclude whether the question refers to GTK3 or GTK4 - therefore this answer covers both cases.

GTK4

GTK4 already supports macOS as a backend. Looking for a corresponding NSApplicationDelegate delegate method in gtkapplication-quartz.c one can see application:openFiles:, so the support is there.

However in line 164 one can see that the delegate is only set if the boolean register_session is true, see the check in line 161.

Following that trace backwards via gtk_application_impl_startup, gtk_application_startup in gtkapplication.c etc. one can see that register_session is a property of the application.

Basically, we need to set this to true to make sure the delegate is set and we are notified of open file attempts.

The property can then be set something like this:

GValue val = G_VALUE_INIT;
g_value_init (&val, G_TYPE_BOOLEAN);
g_value_set_boolean(&val, true);
g_object_set_property(G_OBJECT(app), "register-session", &val);
g_value_unset (&val);

Self-contained GTK4 example

Following on from the above, it could be written something like this:

#include <gtk/gtk.h>

static void
activate (GtkApplication *app) {
    GtkWidget *window = gtk_application_window_new (app);
    GtkWidget *button = gtk_button_new_with_label ("Close App");
    g_signal_connect_swapped (button, "clicked", G_CALLBACK (gtk_window_close), window);
    gtk_window_set_child (GTK_WINDOW (window), button);
    gtk_window_present (GTK_WINDOW (window));
}

static void
app_open (GApplication  *application, GFile **files, gint n_files, const gchar *hint) {
    for (gint i = 0; i < n_files; i++) {
        gchar *uri = g_file_get_uri (files[i]);
        g_print ("open %s\n", uri);
        g_free (uri);
    }
}

int main(int argc, char **argv) {
    GtkApplication *app = gtk_application_new("net.bean.app", G_APPLICATION_HANDLES_OPEN);

    GValue val = G_VALUE_INIT;
    g_value_init (&val, G_TYPE_BOOLEAN);
    g_value_set_boolean(&val, true);
    g_object_set_property(G_OBJECT(app), "register-session", &val);
    g_value_unset (&val);

    g_signal_connect(app, "activate", G_CALLBACK(activate), NULL);
    g_signal_connect(app, "open", G_CALLBACK(app_open), NULL);
    int status = g_application_run(G_APPLICATION(app), argc, argv);
    g_object_unref(app);

    return status;
}

CFBundleTypeExtensions is deprecated

This should work with your .plist file, however CFBundleTypeExtensions is deprecated since OS X v10.5., see https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html

For viewing .jpeg files, you should rather use something like the following:

    <key>CFBundleDocumentTypes</key>
    <array>
        <dict>
            <key>CFBundleTypeName</key>
            <string>JPEG File</string>
            <key>CFBundleTypeRole</key>
            <string>Viewer</string>
            <key>LSHandlerRank</key>
            <string>Alternate</string>
            <key>LSItemContentTypes</key>
            <array>
                <string>public.jpeg</string>
            </array>
        </dict>
    </array>

GTK4 Test

After creating the iViewGTK app bundle, you can run a quick test with the above test program - if you run it from the command line, you can even see the output of prinft on the console:

iViewGTK.app/Contents/MacOS/iViewGTK

When you open a file by right-clicking a .jpeg file in the Finder and choosing Open With/iViewGTK from the context menu, the following appears:

open file:///Users/stephan/tmp/gtk/testsuite/gdk/image-data/iamge-cmyk.jpeg

So there is no error dialog and the file path is successfully passed to the GTK application.

GTK3

In the case of a GTK3 application, there are important notes at https://www.gtk.org/docs/installations/macos:

Linking with GTK’s Quartz backend connects your application to the Mac’s native display manager, keyboard, and pointing device. With a little extra code and gtk-mac-integration you can:

...

  • Receive open events from Finder.
  • ...

Completely self-contained GTK3 example

#include <gtk/gtk.h>
#include <assert.h>
#include <gtkosxapplication.h>

static  GtkWidget *
new_window(void) {
    GtkWidget *window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
    assert (window != NULL);
    gtk_widget_show_all(window);
    return window;
}

static gboolean
app_open_file_cb (GtkosxApplication *app, gchar *path, gpointer user_data) {
    printf("open file '%s'\n", path);
    //do sth with file
    return TRUE;
}

int main(int argc, char **argv) {
    gtk_init (&argc, &argv);
    GtkApplication *app  = g_object_new (GTKOSX_TYPE_APPLICATION, NULL);
    GtkWidget *window = new_window();
    g_signal_connect (app, "NSApplicationOpenFile",
                      G_CALLBACK (app_open_file_cb), NULL);
    gtkosx_application_ready (app);
    gtk_main ();
    g_object_unref(app);
    return 0;
}

GTK3 Test

The same test as above, if you select Open with/iViewGTK in the Finder, the following appears:

gtk open file test

So again no error dialog is shown and the file path is successfully passed to the GTK application.

Upvotes: 2

Related Questions