Making GUI Apps for Linux, Macintosh, and Windows

Published at 21:02 on 3 April 2022

Foreword

I collected this information about two years ago, intending to publish it here, yet never did. Before I lost track of it, I decided to do so this evening. Note that this information was correct as of two years ago; so there is a chance that some things have changed since then. (Though I am at present revisiting these topics, and so far I haven’t found any changes in what follows.)

Introduction

I’ve been mostly a back-end programmer and command-line guy. That, plus inertia, has caused me to not bother with supporting graphical user interfaces in the code I write.

Until recently, that is. There’s a few ideas I’ve had rattling around in my head that would be useful for others, but many of those other people are not computer geeks and would not be interested in opening a Terminal or Command Prompt app just to run my command-line programs.

As a result, I’ve been learning how to make normal, “clickable” apps that a normal person would be able to run without extensive training and hand-holding. Might as well share that knowledge with others.

What follows is likely to be particularly useful to those who, like me, are using something other than the normal, approved tools to code their apps. In my case, I’m using Kotlin, because it’s a modern language with a powerful, expressive syntax and it runs under the Java virtual machine, making it much easier to port my code to different systems.

Yes, there are “standard” programs to do all of the following. Without very few exceptions, I have found them to be poorly documented and geared to C/C++ developers. I found that attempting to bend these tools to my will was sufficiently difficult and painful that it was easier to forget about them and just do it all myself, mostly from scratch.

Linux (Gnome on Ubuntu)

To have an application that comes up as a clickable icon like all the other normal Gnome apps, one must install files in a number of places, primarily under /usr/share. It’s something of a mess, as the files that define the presence of a given app are scattered here and there, not collected in one place as they are on a Mac.

The easiest way to cope with this state of affairs is to do what everyone else does: make a Debian package (Ubuntu is Debian under the hood). Thankfully, .deb files are pretty simple: an ar archive with three members:

debian-binary
This consists of the string “2.0” followed by a newline. That’s it. It must be the first member of the archive.
control.tar.gz
Files that control the installation. The control (yes, control.tar.gz contains a file named control) file is the only mandatory one, though an md5sums one is highly recommended. This must be the second member of the archive.
data.tar.gz
The files to install. They all are relative paths (to root). I.e. if your package has an executable to be installed as /usr/share/program it will be in here as usr/share/program. This must be the third and final member of the archive.

Note that all files must be stored with the correct ownership information (usually as root, the superuser).

In order for a Linux application to look like a normal, clickable Gnome app, and for lintian to not complain a lot about your package, you need to have a number of files installed (i.e. present in data.tar.gz). For the purpose of this list, name refers to your application’s name, folded to lower case:

usr/bin/name
Most clickable apps on Ubuntu are also installed so that they may be invoked via the command line.
usr/share/applications/name.desktop
This is the main file whose presence enables your application to show up on the desktop.
usr/share/doc/name/changelog.gz
A description of the changes made to the package. This is compressed from a file that has a nasty, column-sensitive format; it is described in detail below.
usr/share/doc/name/copyright
A copyright message. If it refers to one of the standard licenses described in /usr/share/common-licenses, you should not include the full license terms in this file but instead end it with a reference to the common license.
usr/share/icons/hicolor/48x48/apps/name.png
At a minimum, you must define 48 × 48 an icon in the hicolor theme. The name of this icon file must match the name of the icon described in the .desktop file; for sanity’s sake, just use your application name.
usr/share/name
If your program has any data files, these go in this directory. For example, the .jar and .class files for a Java application will live here.
usr/share/man/man1/name.1.gz
There should be a manual page for your application, which should be in compressed form.

It is best to use dpkg to inspect a few .deb files for programs similar to yours to get an idea of what you need to define. A good source of such files can be the system package cache, /var/cache/apt/arhives.

Once you have a directory tree with the files you need, it is a simple enough matter to use tar and ar to create a .deb file:

echo 2.0 > debian-binary
cd data
find * -type f -print0 | xargs -0 md5sum > ../control/md5sums
tar -c -z --owner=0 --group=0 -f ../data.tar.gz *
cd ../control
tar -c -z --owner=0 --group=0 -f ../control.tar.gz *
cd ..
ar r name.deb debian-binary control.tar.gz data.tar.gz

After creating your .deb package, it is strongly recommended that you use lintian --info to check it. In general, you should be concerned about anything flagged at the E (error) level, and at least make an effort to reduce the number of W (warning) level messages that lintian reports.

One thing I don’t worry about is complaints from lintian about the manual pages not being compressed to the maximum level: I use an Apache Ant task, not the gzip utility, to generate my compressed files, and it has no option to select maximum compression. It makes no difference to the system, and the amount of space saved by using maximum compression over the normal level is insignificant.

The Changelog File

Welcome to the year 1967. You are at a keypunch machine preparing a data deck for an IBM 360 program. Be sure to follow the correct rules for which records get punched starting in which column, or you will be rewarded with lots of error codes IEBLINTIAN later!

Your changelog deck consists of a series of cards, which contains groups of records which describe changes made to your program. Each group of records must begin with a card punched starting in column 1, of the following format:

name (version) distribution; urgency=urgency

Things in bold should be typed verbatim; things in italics should be replaced with something appropriate:

name
The name of this package, consistent with the name used elsewhere
version
Pretty obvious. The version mumber.
distribution
Until and unless your package becomes a well-established part of the core distribution, this should probably be unstable.
urgency
This will usually be low.

Following this card, you may optionally have a blank card. The details of the change are introduced by a card with an asterisk punched in column three (columns one and two must be blank); the remaining columns on this card describe the change. If describing the change takes more than a single card, subsequent continuation cards are punched starting in column five.

After the description comes an optional blank card, followed by the card defining the programmer and date. This is done by punching hyphens in columns 2 and 3; the card has the following overall format:

  -- Joe Coder <joe@coder.com>  Thu, 30 Apr 2020 13:16:43 -0700

Note that the e-mail address must be in angle brackets, and there must be two blanks separating it from the date, which must be punched in RFC2822 format (the same as reported by the date -R command).

Macintosh

A Macintosh application (technically, an “application bundle”) is actually a directory whose name ends in .app and which contains but a single subdirectory, Contents. That subdirectory in turn must contain a number of files:

Contents/Info.plist
This is an XML document in a specific form, which is described here. If you are developing a Java application, see “Info.plist Java Notes” below. Apple provides a command-line program, /usr/libexec/PlistBuddy, which can be useful when generating or reading this file.
Contents/MacOS/something
This is the executable file for your program. It is OK for it to be an executable script in one of the standard MacOS scripting languages (e.g. a bash script). Its name much match whatever something you chose to associate with the CFBundleExecutable key in Info.plist.
Contents/PkgInfo
I have not been able to find much information on this file, but I believe it helps the Mac associate this application with certain file types. In the general case (no special associations required) it suffices to set the contents of this file to APPL???? .
Contents/Resources/something.icns
The application’s icons. See “Preparing Icons” below for more details on how to create this file. This file’s name must match whatever something you chose to associate with the CFBundleIconFile key in Info.plist.

That’s it for the mandatory contents. Any directory whose name ends in .app and contains the above structure should be recognized as a clickable application by the Macintosh. It is, of course, common for applications to contain read-only data, which is also contained inside the app bundle. For example, .jar and .class files for Java applications can be stored in Contents/Java. Applications can also use non-reserved keys in Info.plist to store configuration information and other data.

Info.plist Java Notes

Apple has a standard way of storing Java-specific information in Info.plist, under a Java element in the top-level dictionary. Unfortunately, using it will cause a Mac to attempt to run the app using a very old, Apple-customized Java runtime that isn’t even present on most Macs. Your users will see a dialog with a bunch of blather about the “legacy Java runtime” being needed, and even if they follow Apple’s suggestion and download that runtime, it is likely that won’t be able to run your application, because it is so obsolete.

Therefore, as Groucho Marx said about the doctor’s response to the patient who complained “It hurts when I do this,” don’t do that. I follow the Oracle convention of putting the entry-point class in JVMMainClassName, a package-relative path to my Jar file in JVMClassPath, a Java version specification in JVMVersion, and any extra options to pass to the Java environment in a JVMOptions array.

Actually, it doesn’t much matter. I could have come up with unique keys of my own (so long as they didn’t clash with any official Apple ones), and it would have worked just as well. MacOS is blissfully unaware of the significance of those JVM… tags, and simply ignores them. They are meaningful only to my defined CFBundleExecutable script, which has been coded to look in Info.plist for some of its options.

Preparing Icons

Mac application icons are stored in .icns files. These are actually a collection of multiple icons, defined for a variety of sizes. To create such a file, you must create a directory whose name ends in .iconset, and populate it with PNG images containing your icon in various sizes, as described here. Then use iconutil to generate a .icns file; assuming your directory was named name.iconset, you would type:

iconutil -c icns name

Windows

A clickable app on Windows is simply an executable (.EXE) file that contains an embedded icon which Windows will recognize and display.

If you create a jar file and set Main-Class in its manifest to point to the class containing your application’s entry point, Windows considers it to be an “executable jar” and will launch your application when you click on the jar file. That’s almost as good as a having a proper executable with an embedded icon, but it doesn’t have an embedded icon, so your app will display using the default icon that all jar files get.

The solution is to install Launch4j and use it to create a Windows executable with your icon of choice embedded in it. This is a free program, and I have found it to be well-documented.

If your application is sufficiently complex as to require a bunch of support files under Windows, then you will need to create a Microsoft Installer file. My apps have so far been simple enough not to require this, so I don’t have much help to offer in this regard yet.

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.