Developing cross-platform desktop applications in the age of Mobile and the Web

by Anton Patrushev

Web and apps are everywhere. Your phone runs your apps. One of the apps is the browser: you use it in situations when you don’t have a suitable app for the task at hand. There are millions of apps and they provide seemingly limitless functionality. All true. But there are limits that no app will surpass for you: the physical limits of your device. No app will make your screen bigger and no app will provide you with physical keyboard and mouse. And indeed there are tasks that absolutely require these things. So we are back to the PC or the laptop. Wait a minute… there are no apps on my PC! There are plain old applications and the Web.

“Just” ten years ago, you could easily jump from thinking “I need a desktop application” directly to “I need a Windows application”. This is not the case any longer. The market share of Windows is shrinking. The Macs are aggressively taking over users from Microsoft’s promised land of PC. Even though “Linux Desktop” is considered a joke in certain circles, it definitely exists in developing markets as well as in many notable public office initiatives such as the City of Munich. So our question today is how to develop an application that would run on the three dominant desktop platforms — Windows, Mac, and Linux — natively, and, hopefully, without programming it three times.

In the past few years, the Web kept itself busy becoming more powerful, more feature-rich, more responsive, … more like what you can expect from a native application on your respective platform. Very smart people have decided that the Web is already providing native experience and they have come up with beautiful solutions, such as Electron, that make web applications in JavaScript run in a window that mimics the standard interface of native applications. Furthermore, they expose more operating system resources to the JavaScript application so it can do many things that actual web application couldn’t dream of. Yet the future is not yet here and electron applications feel like a slightly better browser window. The experience is not native at all; the grasp of operating system resources is not full; and you need to program in JavaScript which is not necessarily your idea of being proud of what you are doing.

So we are back to proper application development. The good news is that practically every programming language nowadays will run on practically all operating systems. The concepts of I/O, process control and networking are mature and therefore similar across the board. The bad news is that user interface (UI) is an area of much experimentation, marketing claims and malicious patenting. Put simply, the area of UI is not mature and different not only across operating systems but also sometimes within one operating system. It may be said, rightly, that UI is not operating systems’ business. Still the fact remains: there are many different approaches to UI and making an application that looks natively in all operating systems and on all UI paradigms is a hard task.

Having put aside the idea of running some sort of a web application on the desktop earlier, let’s see what other cross-platform options do we have left. We can use one of the available ones in Java world; we can use wxWidgets; and we can use Qt.


There is not one but all of the three (maybe more!) options to do desktop UI in Java: swt (and Eclipse RCP), JavaFX and Swing. Swing works everywhere but it neither looks nor feels native on any platform. The feeling is that Swing is actually coming out of the times when Java was supposed to take over the world completely on desktop, server and the web so somebody had an idea to put together Java’s very own UI approach. Didn’t quite work out the way they planned.

JavaFX is a strategic replacement for Swing from Oracle. It shares the same issues with native look and feel and it comes with partially closed, Oracle-owned source code, a serious investment risk.

Finally, swt works. swt looks native. swt is cool. swt is what RCP/Eclipse is built on. If your choice of development platform is Java, you should seriously consider swt. Just a few things to keep in mind. The community for “bare” swt is very small. When you run into problems, you will probably find answers for how to overcome those using RCP. But what if you don’t want RCP’s monstrosity in your application? Also, if you care about Linux, note that out of the box, swt will only work well on GTK.

Summarizing, it is indeed possible to develop a multi-platform desktop application in Java but it makes sense only if the rest of your application is in Java as well. Out of the existing approaches we recommend using swt. Prepare to be on your own with any problems though.


wxWidgets look native by default. No wonder — they are native widgets of the operating system controlled through a common API layer and countless adapters. This approach provides good results if the number of your target platforms is low (as in “two”). When this number starts to grow, it becomes an uphill battle for developers to keep up. Compromise decisions have to be taken. Such as to only actually support GTK+ on Linux. wxWidgets claims to be mature and have good bindings in Python, Perl and Ruby. In reality, Python support is some still-working-but-obsolete code plus already-working-but-unfinished rewrite initiative. Documentation is lacking. Community is suffering from platform fragmentation and not very strong in general. The amount of information on wxWidgets programming is totally inadequate for a code base of this age and claims.


And now we are coming to the solution we have chosen: Qt. Qt does not attempt to use native widgets, it draws its own but styles exist to make them look very much like native widgets of all target platforms. It comes under LGPL or an affordable commercial license. It has excellent, mature documentation and established (but still vibrant!) community. It runs smoothly on all platforms, extending also to some of the mobile ones. Qt is developed in C++ and its original intention is to be a UI library for a C++ programmer. But you can use Qt via existing bindings from many languages. Python is author’s preference because it is highly expressive, comes “with batteries included” and features excellent package and dependency managers. Using Qt with Python is so popular that Python has two mature bindings, PyQt and PySide. Bindings differ in versions of Python and Qt that they support and in licensing terms; commercial support is offered for PyQt developers.

PyQt appears to be more mature and well-supported but opinions here differ to the point of religious wars sparking from time to time in the corners of Internet. In our experience, development with PyQt is straightforward for a Python programmer; PyQt also supports standard Python packaging and distribution patterns. Add PyInstaller to the mix — and you have native executables for all the platforms we discussed.

Getting your PyQt program to run on Windows, Mac and Linux

Now that we have established that cross-platform desktop programming is to be done in Python using Qt via PyQt, let us walk you through getting your own Python+Qt+PyQt code to run on the said three platforms.

We are assuming that the reader is generally familiar with Python programming and package management system, essentials of which are outside of the scope of this tutorial.

Installing dependencies

You will need a working installation of Python3. To obtain it, follow the official installation instructions or any other way appropriate for your environment and operating system.

To configure your environment you need to install mandatory components. You can do it in a virtual environment so not to require elevated permissions and not to pollute your system:

$ python3 -m venv venv 
$ venv/bin/pip3 install PyQt5

Here and ahead, if you are on Windows (sorry!), /bin needs to be replaced with /Scripts and “python3” with simply “python” (but it needs to be Python 3)

We will use PyInstaller to package our native applications:

$ venv/bin/pip3 install pyinstaller

There is currently a quirk in PyInstaller v3.2, it is unable to build PyQt5-based applications for OSX. If you need OSX binaries, install a version from the current development branch.

We are linking to a specific state of the code simply because it is what is known to be working but not really named in any way by developers. The reader is by all means encouraged to try the latest release of PyInstaller or latest code from development branch at the time of reading.

Let’s start

Let’s create a simple application and build it into executables for our three target operating systems, Windows, OSX and Linux. It will be a very simple text editor that goes by Qute. It is so small the PyQt5 code is the short snippet below (

Right now we can already try our application:

$ venv/bin/python3

MainWindow class initializes all widgets and layout of our small app, that’s all we need for now. For more serious applications you can use Qt Designer to create UI layout. It produces the form layout in a custom xml format which later can be compiled to python code with pyuic5 tool.

So we are ready to pack our cute Qute editor into a binary application for target platforms. Let’s create an application with PyInstaller:

$ venv/bin/pyinstaller -w

You will get:

  • On OSX you will find your app in dist subfolder as;
  • On Windows it will be also in dist folder as bunch of files;
  • On Linux it is better to build one-file application by adding -F option to PyInstaller command line:
venv/bin/pyinstaller -wF

During initial build, PyInstaller creates a spec file that can be used later to customize your application build process (for instance, to default to one-file/one-dir behaviour.) PyInstaller can then be called with predefined configuration:

venv/bin/pyinstaller main.spec

It is important to note that PyInstaller processes the spec files with Python interpreter so you can include complex dependencies to collect assets of your application. Here is the spec file generated for our app, stripped down for the sake of readability:

We can change it to build one-file app for Linux platform:

With such a spec file you can build your application with the same command regardless of the particularities of the target operating systems:

pyinstaller main.spec

Now that we have a binary, we can package it using your favourite installer packager on your target operating system and provide the result to the user.


It is still pretty easy to build cross-platform desktop applications. By using QT/Python/PyQt/PyInstaller technology stack you will get mature environment with lots of batteries included to concentrate on the core functionality of your application.

by Anton Patrushev

Do you have further questions about Python and Qt? Write to us!

Do you want to work with us and learn these and other technologies while making the world a better place? Tell us about yourself and your interest

Written by

Technology to build the Earth’s sustainable future is here. is materialising the potential that IT can bring into global sustainability efforts.

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store