{"id":9031,"date":"2021-12-30T22:45:09","date_gmt":"2021-12-30T21:45:09","guid":{"rendered":"https:\/\/monodes.com\/predaelli\/?p=9031"},"modified":"2021-12-30T22:45:09","modified_gmt":"2021-12-30T21:45:09","slug":"packaging-pyqt5-apps-with-fbs-distribute-cross-platform-gui-applications-with-the-fman-build-system","status":"publish","type":"post","link":"https:\/\/monodes.com\/predaelli\/2021\/12\/30\/packaging-pyqt5-apps-with-fbs-distribute-cross-platform-gui-applications-with-the-fman-build-system\/","title":{"rendered":"Packaging PyQt5 apps with fbs \u2014 Distribute cross-platform GUI applications with the fman Build System"},"content":{"rendered":"<p><em><a href=\"https:\/\/www.pythonguis.com\/tutorials\/packaging-pyqt5-apps-fbs\/\">Packaging PyQt5 apps with fbs \u2014 Distribute cross-platform GUI applications with the fman Build System<\/a><\/em><\/p>\n<p><!--more--><!--nextpage--><\/p>\n<blockquote>\n<div id=\"container-top\" class=\"pure-g\">\n<div id=\"content-upper\" class=\"pure-u-1-1 l-box \">\n<div class=\"pure-u-1-1 article-header\">\n<div class=\" article-header-center\">\n<h1>Packaging PyQt5 apps with fbs<br \/>\n<small> Distribute cross-platform GUI applications with the fman Build System<\/small><\/h1>\n<\/div>\n<\/div>\n<\/div>\n<\/div>\n<div id=\"container\" class=\"pure-g\">\n<div class=\"pure-u-1-1 pure-u-md-17-24 l-box\">\n<div class=\"byline\"><span class=\"byline-part\"> by <a href=\"https:\/\/www.pythonguis.com\/authors\/martin-fitzpatrick\/\">Martin Fitzpatrick<\/a><\/span> <span class=\"byline-part\"><i class=\"fas fa-stopwatch\"><\/i> Read time 13:29<\/span> <span class=\"byline-part\"><i class=\"fas fa-folder\"><\/i> <a href=\"https:\/\/www.pythonguis.com\/courses\/packaging-and-distribution\/\"> Packaging and distribution <\/a><\/span><\/div>\n<div class=\"article-curriculum\">\n<h4>Packaging and distribution course<\/h4>\n<ul class=\"course-lessons fa-ul\">\n<li class=\"lesson-row\"><i class=\"far fa-circle\"><\/i> <a href=\"https:\/\/www.pythonguis.com\/tutorials\/packaging-pyqt5-pyside2-applications-windows-pyinstaller\/\">Packaging PyQt5 applications for Windows, with PyInstaller<\/a><\/li>\n<li class=\"lesson-row\"><i class=\"fas fa-hand-point-right\"><\/i> <a href=\"https:\/\/www.pythonguis.com\/tutorials\/packaging-pyqt5-apps-fbs\/\">Packaging PyQt5 apps with fbs<\/a><\/li>\n<\/ul>\n<p class=\"crosslinks\">This tutorial is also available for <a class=\"crosslink-tag\" href=\"https:\/\/www.pythonguis.com\/tutorials\/pyside-packaging-apps-fbs\/\">PySide2<\/a><\/p>\n<\/div>\n<article class=\"post\"><a name=\"top\"><\/a><\/p>\n<div id=\"article-main-body\" class=\"article-wrap  article-main-body\">\n<p><strong>fbs<\/strong> is a cross-platform PyQt5 packaging system which supports building desktop applications for Windows, Mac and Linux (Ubuntu, Fedora and Arch). Built on top of <em>PyInstaller<\/em> it wraps some of the rough edges and defines a standard project structure which allows the build process to be entirely automated. The included resource API is particularly useful, simplifying the handling of external data files, images or third-party libraries \u2014\u00a0a common pain point when bundling apps.<\/p>\n<p>This tutorial will take you through the steps of creating PyQt5 applications using <strong>fbs<\/strong> from scratch, and for converting existing projects over to the system. If you\u2019re targeting multiple platforms with your app, it&#8217;s definitely worth a look.<\/p>\n<p>If you&#8217;re impatient, you can grab the Moonsweeper installers directly for <a href=\"http:\/\/download.mfitzp.com\/MoonsweeperSetup.exe\">Windows<\/a>, <a href=\"http:\/\/download.mfitzp.com\/Moonsweeper.dmg\">MacOS<\/a> or <a href=\"http:\/\/download.mfitzp.com\/Moonsweeper.deb\">Linux (Ubuntu)<\/a>.<\/p>\n<p class=\"admonition admonition-info\"><i class=\"fas fa-info\"><\/i> <strong>fbs<\/strong> is licensed under the GPL. This means you can use the <strong>fbs<\/strong> system for free in packages distributed with the GPL. For commercial (or other non-GPL) packages you must buy a commercial license. See the fbs licensing page for up-to-date information.<\/p>\n<p class=\"admonition admonition-tip\"><i class=\"fas fa-lightbulb\"><\/i> <strong>fbs<\/strong> is built on top of PyInstaller. You can also use PyInstaller directly to package applications, see our <a href=\"https:\/\/www.pythonguis.com\/tutorials\/packaging-pyqt5-pyside2-applications-windows-pyinstaller\/\">Packaging PyQt5 &amp; PySide2 applications for Windows, with PyInstaller<\/a> tutorial.<\/p>\n<h2 id=\"install-requirements\">Install requirements<\/h2>\n<p><strong>fbs<\/strong> works out of the box with both PyQt <code class=\"\" data-line=\"\">PyQt5<\/code> and Qt for Python <code class=\"\" data-line=\"\">PySide2<\/code>. The only other requirement is <code class=\"\" data-line=\"\">PyInstaller<\/code> which handles the packaging itself. You can install these in a virtual environment (or your applications virtual environment) to keep your environment clean.<\/p>\n<p class=\"admonition admonition-note\"><i class=\"fas fa-pencil-alt\"><\/i> fbs only supports Python versions 3.5 and 3.6<\/p>\n<div class=\"code-block\"><span class=\"code-block-language code-block-bash\">bash<\/span><\/p>\n<pre><code class=\"\" data-line=\"\">python3 -m venv fbsenv\n<\/code><\/pre>\n<\/div>\n<p>Once created, activate the virtual environment by running from the command line \u2014<\/p>\n<div class=\"code-block\"><span class=\"code-block-language code-block-bash\">bash<\/span><\/p>\n<pre><code class=\"\" data-line=\"\">&lt;span class=&quot;hljs-comment&quot;&gt;# On Mac\/Linux:&lt;\/span&gt;\n&lt;span class=&quot;hljs-built_in&quot;&gt;source&lt;\/span&gt; fbsenv\/bin\/activate\n\n&lt;span class=&quot;hljs-comment&quot;&gt;# On Windows:&lt;\/span&gt;\ncall fbsenv\\scripts\\activate.bat\n<\/code><\/pre>\n<\/div>\n<p>Finally, install the required libraries. For PyQt5 you would use \u2014<\/p>\n<div class=\"code-block\"><span class=\"code-block-language code-block-python\">python<\/span><\/p>\n<pre><code class=\"\" data-line=\"\">pip3 install fbs PyQt5 PyInstaller==&lt;span class=&quot;hljs-number&quot;&gt;3.4&lt;\/span&gt;\n<\/code><\/pre>\n<\/div>\n<p>Or for Qt for Python (PySide2) \u2014<\/p>\n<div class=\"code-block\"><span class=\"code-block-language code-block-python\">python<\/span><\/p>\n<pre><code class=\"\" data-line=\"\">pip3 install fbs PySide2 PyInstaller==&lt;span class=&quot;hljs-number&quot;&gt;3.4&lt;\/span&gt;\n<\/code><\/pre>\n<\/div>\n<p><strong>fbs<\/strong> installs a command line tool <code class=\"\" data-line=\"\">fbs<\/code> into your path which provides access to all <strong>fbs\u00a0<\/strong> management commands. To see the complete list of commands available run <code class=\"\" data-line=\"\">fbs<\/code>.<\/p>\n<div class=\"code-block\"><span class=\"code-block-language code-block-bash\">bash<\/span><\/p>\n<pre><code class=\"\" data-line=\"\">martin@Martins-Laptop testapp $ fbs\nusage: fbs [-h]\n           {startproject,run,freeze,installer,sign_installer,repo,\n           upload,release,&lt;span class=&quot;hljs-built_in&quot;&gt;test&lt;\/span&gt;,clean,buildvm,runvm,gengpgkey,register,login,init_licensing}\n           ...\n\nfbs\n\npositional arguments:\n  {startproject,run,freeze,installer,sign_installer,repo,\n  upload,release,&lt;span class=&quot;hljs-built_in&quot;&gt;test&lt;\/span&gt;,clean,buildvm,runvm,gengpgkey,register,login,init_licensing}\n    startproject        Start a new project &lt;span class=&quot;hljs-keyword&quot;&gt;in&lt;\/span&gt; the current directory\n    run                 Run your app from &lt;span class=&quot;hljs-built_in&quot;&gt;source&lt;\/span&gt;\n    freeze              Compile your code to a standalone executable\n    installer           Create an installer &lt;span class=&quot;hljs-keyword&quot;&gt;for&lt;\/span&gt; your app\n    sign_installer      Sign installer, so the user&lt;span class=&quot;hljs-string&quot;&gt;&#039;s OS trusts it\n    repo                Generate files for automatic updates\n    upload              Upload installer and repository to fbs.sh\n    release             Bump version and run clean,freeze,...,upload\n    test                Execute your automated tests\n    clean               Remove previous build outputs\n    buildvm             Build a Linux VM. Eg.: buildvm ubuntu\n    runvm               Run a Linux VM. Eg.: runvm ubuntu\n    gengpgkey           Generate a GPG key for Linux code signing\n    register            Create an account for uploading your files\n    login               Save your account details to secret.json\n    init_licensing      Generate public\/private keys for licensing\n\noptional arguments:\n  -h, --help            show this help message and exit\n&lt;\/span&gt;<\/code><\/pre>\n<\/div>\n<p>Now you&#8217;re ready to start packaging applications with <strong>fbs<\/strong>.<\/p>\n<h2 id=\"starting-an-app\">Starting an app<\/h2>\n<p>If you\u2019re starting a PyQt5 application from scratch, you can use the <code class=\"\" data-line=\"\">fbs startproject<\/code> management command to create a complete, working and packageable application stub in the current folder. This has the benefit of allowing you to test (and continue to test) the packageability of your application as you develop it, rather than leaving it to the end.<\/p>\n<div class=\"code-block\"><span class=\"code-block-language code-block-bash\">bash<\/span><\/p>\n<pre><code class=\"\" data-line=\"\">fbs startproject\n<\/code><\/pre>\n<\/div>\n<p>The command walks you through a few questions, allowing you to fill in details of your application. These values will be written into your app source and configuration. The bare-bones app will be created under the <code class=\"\" data-line=\"\">src\/<\/code> folder in the current directory.<\/p>\n<div class=\"code-block\"><span class=\"code-block-language code-block-bash\">bash<\/span><\/p>\n<pre><code class=\"\" data-line=\"\">martin@Martins-Laptop ~ $ fbs startproject\nApp name [MyApp] : HelloWorld\nAuthor [Martin] : Martin Fitzpatrick\nMac bundle identifier (eg. com.martin.helloworld, optional):\n<\/code><\/pre>\n<\/div>\n<p class=\"admonition admonition-note\"><i class=\"fas fa-pencil-alt\"><\/i> If you already have your own working PyQt5 app you will need to either a) use the generated app as a guideline for converting yours to the same structure, or b) create a new app using `startproject` and migrate the code over.<\/p>\n<h3>Running your new project<\/h3>\n<p>You can run this new application using the following <strong>fbs<\/strong> command in the same folder you ran <code class=\"\" data-line=\"\">startproject<\/code> from.<\/p>\n<div class=\"code-block\"><span class=\"code-block-language code-block-bash\">bash<\/span><\/p>\n<pre><code class=\"\" data-line=\"\">fbs run\n<\/code><\/pre>\n<\/div>\n<p>If everything is working this should show you a small empty window with your apps&#8217; title \u2014 exciting eh?<\/p>\n<p><img data-recalc-dims=\"1\" decoding=\"async\" src=\"https:\/\/i0.wp.com\/monodes.com\/predaelli\/wp-content\/uploads\/sites\/4\/2021\/12\/hello-world-windows.png?w=910&#038;ssl=1\" alt=\"Hello World App on Windows\" \/> <em>Hello World App on Windows<\/em><\/p>\n<p><img data-recalc-dims=\"1\" decoding=\"async\" src=\"https:\/\/i0.wp.com\/monodes.com\/predaelli\/wp-content\/uploads\/sites\/4\/2021\/12\/hello-world-mac.png?w=910&#038;ssl=1\" alt=\"Hello World App on Mac\" \/> <em>Hello World App on Mac<\/em><\/p>\n<p><img data-recalc-dims=\"1\" decoding=\"async\" src=\"https:\/\/i0.wp.com\/monodes.com\/predaelli\/wp-content\/uploads\/sites\/4\/2021\/12\/hello-world-ubuntu.png?w=910&#038;ssl=1\" alt=\"Hello World App on Linux\" \/> <em>Hello World App on Linux<\/em><\/p>\n<h3>The application structure<\/h3>\n<p>The <code class=\"\" data-line=\"\">startproject<\/code> command generates the required folder structure for a <strong>fbs<\/strong> PyQt5 application. This includes a <code class=\"\" data-line=\"\">src\/build<\/code> which contains the build settings for your package, <code class=\"\" data-line=\"\">main\/icons<\/code> which contains the application icons, and <code class=\"\" data-line=\"\">src\/python<\/code> for the source.<\/p>\n<div class=\"code-block\"><span class=\"code-block-language code-block-bash\">bash<\/span><\/p>\n<pre><code class=\"\" data-line=\"\">.\n\u2514\u2500\u2500 src\n    \u251c\u2500\u2500 build\n    \u2502\u00a0\u00a0 \u2514\u2500\u2500 settings\n    \u2502\u00a0\u00a0     \u251c\u2500\u2500 base.json\n    \u2502\u00a0\u00a0     \u251c\u2500\u2500 linux.json\n    \u2502\u00a0\u00a0     \u2514\u2500\u2500 mac.json\n    \u2514\u2500\u2500 main\n        \u251c\u2500\u2500 icons\n        \u2502\u00a0\u00a0 \u251c\u2500\u2500 Icon.ico\n        \u2502\u00a0\u00a0 \u251c\u2500\u2500 README.md\n        \u2502\u00a0\u00a0 \u251c\u2500\u2500 base\n        \u2502\u00a0\u00a0 \u2502\u00a0\u00a0 \u251c\u2500\u2500 16.png\n        \u2502\u00a0\u00a0 \u2502\u00a0\u00a0 \u251c\u2500\u2500 24.png\n        \u2502\u00a0\u00a0 \u2502\u00a0\u00a0 \u251c\u2500\u2500 32.png\n        \u2502\u00a0\u00a0 \u2502\u00a0\u00a0 \u251c\u2500\u2500 48.png\n        \u2502\u00a0\u00a0 \u2502\u00a0\u00a0 \u2514\u2500\u2500 64.png\n        \u2502\u00a0\u00a0 \u251c\u2500\u2500 linux\n        \u2502\u00a0\u00a0 \u2502\u00a0\u00a0 \u251c\u2500\u2500 1024.png\n        \u2502\u00a0\u00a0 \u2502\u00a0\u00a0 \u251c\u2500\u2500 128.png\n        \u2502\u00a0\u00a0 \u2502\u00a0\u00a0 \u251c\u2500\u2500 256.png\n        \u2502\u00a0\u00a0 \u2502\u00a0\u00a0 \u2514\u2500\u2500 512.png\n        \u2502\u00a0\u00a0 \u2514\u2500\u2500 mac\n        \u2502\u00a0\u00a0     \u251c\u2500\u2500 1024.png\n        \u2502\u00a0\u00a0     \u251c\u2500\u2500 128.png\n        \u2502\u00a0\u00a0     \u251c\u2500\u2500 256.png\n        \u2502\u00a0\u00a0     \u2514\u2500\u2500 512.png\n        \u2514\u2500\u2500 python\n            \u2514\u2500\u2500 main.py\n\n<\/code><\/pre>\n<\/div>\n<p>Your bare-bones PyQt5 application is generated in <code class=\"\" data-line=\"\">src\/main\/python\/main.py<\/code> and is a complete working example you can use to base your own code on.<\/p>\n<div class=\"code-block\"><span class=\"code-block-language code-block-python\">python<\/span><\/p>\n<pre><code class=\"\" data-line=\"\">&lt;span class=&quot;hljs-keyword&quot;&gt;from&lt;\/span&gt; fbs_runtime.application_context.PyQt5 &lt;span class=&quot;hljs-keyword&quot;&gt;import&lt;\/span&gt; ApplicationContext\n&lt;span class=&quot;hljs-keyword&quot;&gt;from&lt;\/span&gt; PyQt5.QtWidgets &lt;span class=&quot;hljs-keyword&quot;&gt;import&lt;\/span&gt; QMainWindow\n\n&lt;span class=&quot;hljs-keyword&quot;&gt;import&lt;\/span&gt; sys\n\n&lt;span class=&quot;hljs-class&quot;&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;class&lt;\/span&gt; &lt;span class=&quot;hljs-title&quot;&gt;AppContext&lt;\/span&gt;(&lt;span class=&quot;hljs-params&quot;&gt;ApplicationContext&lt;\/span&gt;):&lt;\/span&gt;           &lt;span class=&quot;hljs-comment&quot;&gt;# 1. Subclass ApplicationContext&lt;\/span&gt;\n    &lt;span class=&quot;hljs-function&quot;&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;def&lt;\/span&gt; &lt;span class=&quot;hljs-title&quot;&gt;run&lt;\/span&gt;(&lt;span class=&quot;hljs-params&quot;&gt;self&lt;\/span&gt;):&lt;\/span&gt;                              &lt;span class=&quot;hljs-comment&quot;&gt;# 2. Implement run()&lt;\/span&gt;\n        window = QMainWindow()\n        version = self.build_settings[&lt;span class=&quot;hljs-string&quot;&gt;&#039;version&#039;&lt;\/span&gt;]\n        window.setWindowTitle(&lt;span class=&quot;hljs-string&quot;&gt;&quot;HelloWorld v&quot;&lt;\/span&gt; + version)\n        window.resize(&lt;span class=&quot;hljs-number&quot;&gt;250&lt;\/span&gt;, &lt;span class=&quot;hljs-number&quot;&gt;150&lt;\/span&gt;)\n        window.show()\n        &lt;span class=&quot;hljs-keyword&quot;&gt;return&lt;\/span&gt; self.app.exec_()                 &lt;span class=&quot;hljs-comment&quot;&gt;# 3. End run() with this line&lt;\/span&gt;\n\n&lt;span class=&quot;hljs-keyword&quot;&gt;if&lt;\/span&gt; __name__ == &lt;span class=&quot;hljs-string&quot;&gt;&#039;__main__&#039;&lt;\/span&gt;:\n    appctxt = AppContext()                      &lt;span class=&quot;hljs-comment&quot;&gt;# 4. Instantiate the subclass&lt;\/span&gt;\n    exit_code = appctxt.run()                   &lt;span class=&quot;hljs-comment&quot;&gt;# 5. Invoke run()&lt;\/span&gt;\n    sys.exit(exit_code)\n<\/code><\/pre>\n<\/div>\n<p>If you\u2019ve built PyQt5 applications before you\u2019ll notice that building an application with <strong>fbs<\/strong> introduces a new concept \u2014\u00a0the <code class=\"\" data-line=\"\">ApplicationContext<\/code>.<\/p>\n<h3>The <code class=\"\" data-line=\"\">ApplicationContext<\/code><\/h3>\n<p>When building PyQt5 applications there are typically a number of components or resources that are used throughout your app. These are commonly stored in the <code class=\"\" data-line=\"\">QMainWindow<\/code> or as global vars which can get a bit messy as your application grows. The <code class=\"\" data-line=\"\">ApplicationContext<\/code> provides a central location for initialising and storing these components, as well as providing access to some core <strong>fbs<\/strong> features.<\/p>\n<p>The <code class=\"\" data-line=\"\">ApplicationContext<\/code> object also creates and holds a reference to a global <code class=\"\" data-line=\"\">QApplication<\/code> object \u2014 available under <code class=\"\" data-line=\"\">ApplicationContext.app<\/code>. Every Qt application must have one (and only one) <code class=\"\" data-line=\"\">QApplication<\/code> to hold the event loop and core settings. Without <strong>fbs<\/strong> you would usually define this at the base of your script, and call <code class=\"\" data-line=\"\">.exec()<\/code> to start the event loop.<\/p>\n<p>Without <strong>fbs<\/strong> this would look something like this \u2014<\/p>\n<div class=\"code-block\"><span class=\"code-block-language code-block-python\">python<\/span><\/p>\n<pre><code class=\"\" data-line=\"\">&lt;span class=&quot;hljs-keyword&quot;&gt;if&lt;\/span&gt; __name__ == &lt;span class=&quot;hljs-string&quot;&gt;&#039;__main__&#039;&lt;\/span&gt;:\n    app = QApplication()\n    w = MyCustomWindow()\n    app.exec_()\n<\/code><\/pre>\n<\/div>\n<p>The equivalent with <strong>fbs<\/strong> would be \u2014<\/p>\n<div class=\"code-block\"><span class=\"code-block-language code-block-python\">python<\/span><\/p>\n<pre><code class=\"\" data-line=\"\">&lt;span class=&quot;hljs-keyword&quot;&gt;if&lt;\/span&gt; __name__ == &lt;span class=&quot;hljs-string&quot;&gt;&#039;__main__&#039;&lt;\/span&gt;:\n    ctx = ApplicationContext()\n    w = MyCustomWindow()\n    ctx.app.exec_()\n<\/code><\/pre>\n<\/div>\n<p class=\"admonition admonition-note\"><i class=\"fas fa-pencil-alt\"><\/i> If you want to create your own custom `QApplication` initialisation you can overwrite the `.app` property on your `ApplicationContext` subclass using `cached_property` (see below).<\/p>\n<p>This basic example is clear to follow. However, once you start adding custom styles and translations to your application the initialisation can grow quite a bit. To keep things nicely structured <strong>fbs<\/strong> recommends creating a <code class=\"\" data-line=\"\">.run<\/code> method on your <code class=\"\" data-line=\"\">ApplicationContext<\/code>.<\/p>\n<p>This method should handle the setup of your application, such as creating and showing a window, finally starting up the event loop on the <code class=\"\" data-line=\"\">.app<\/code> object. This final step is performed by calling <code class=\"\" data-line=\"\">self.app.exec_()<\/code> at the end of the method.<\/p>\n<div class=\"code-block\"><span class=\"code-block-language code-block-python\">python<\/span><\/p>\n<pre><code class=\"\" data-line=\"\">&lt;span class=&quot;hljs-class&quot;&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;class&lt;\/span&gt; &lt;span class=&quot;hljs-title&quot;&gt;AppContext&lt;\/span&gt;(&lt;span class=&quot;hljs-params&quot;&gt;ApplicationContext&lt;\/span&gt;):&lt;\/span&gt;\n    &lt;span class=&quot;hljs-function&quot;&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;def&lt;\/span&gt; &lt;span class=&quot;hljs-title&quot;&gt;run&lt;\/span&gt;(&lt;span class=&quot;hljs-params&quot;&gt;self&lt;\/span&gt;):&lt;\/span&gt;\n        ...\n        &lt;span class=&quot;hljs-keyword&quot;&gt;return&lt;\/span&gt; self.app.exec_()\n<\/code><\/pre>\n<\/div>\n<p>As your initialisation gets more complicated you can break out subsections into separate methods for clarity, for example \u2014<\/p>\n<div class=\"code-block\"><span class=\"code-block-language code-block-python\">python<\/span><\/p>\n<pre><code class=\"\" data-line=\"\">&lt;span class=&quot;hljs-class&quot;&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;class&lt;\/span&gt; &lt;span class=&quot;hljs-title&quot;&gt;AppContext&lt;\/span&gt;(&lt;span class=&quot;hljs-params&quot;&gt;ApplicationContext&lt;\/span&gt;):&lt;\/span&gt;\n    &lt;span class=&quot;hljs-function&quot;&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;def&lt;\/span&gt; &lt;span class=&quot;hljs-title&quot;&gt;run&lt;\/span&gt;(&lt;span class=&quot;hljs-params&quot;&gt;self&lt;\/span&gt;):&lt;\/span&gt;\n        self.setup_fonts()\n        self.setup_styles()\n        self.setup_translations()\n        &lt;span class=&quot;hljs-keyword&quot;&gt;return&lt;\/span&gt; self.app.exec_()\n\n    &lt;span class=&quot;hljs-function&quot;&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;def&lt;\/span&gt; &lt;span class=&quot;hljs-title&quot;&gt;setup_fonts&lt;\/span&gt;(&lt;span class=&quot;hljs-params&quot;&gt;self&lt;\/span&gt;):&lt;\/span&gt;\n        &lt;span class=&quot;hljs-comment&quot;&gt;#\u00a0...do something ...&lt;\/span&gt;\n\n    &lt;span class=&quot;hljs-function&quot;&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;def&lt;\/span&gt; &lt;span class=&quot;hljs-title&quot;&gt;setup_styles&lt;\/span&gt;(&lt;span class=&quot;hljs-params&quot;&gt;self&lt;\/span&gt;):&lt;\/span&gt;\n        &lt;span class=&quot;hljs-comment&quot;&gt;#\u00a0...do something ...&lt;\/span&gt;\n\n    &lt;span class=&quot;hljs-function&quot;&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;def&lt;\/span&gt; &lt;span class=&quot;hljs-title&quot;&gt;setup_translations&lt;\/span&gt;(&lt;span class=&quot;hljs-params&quot;&gt;self&lt;\/span&gt;):&lt;\/span&gt;\n        &lt;span class=&quot;hljs-comment&quot;&gt;#\u00a0...do something ...&lt;\/span&gt;\n\n<\/code><\/pre>\n<\/div>\n<p class=\"admonition admonition-note\"><i class=\"fas fa-pencil-alt\"><\/i> On execution the `.run()` method will be called and your event loop started. Execution continues in this event loop until the application is exited, at which point your `.run()` method will return (with the appropriate exit code).<\/p>\n<h2 id=\"building-a-real-application\">Building a real application<\/h2>\n<p>The bare-bones application doesn\u2019t do very much, so below we\u2019ll look at something more complete \u2014 the <em>Moonsweeper<\/em> application from my <a href=\"http:\/\/github.com\/mfitzp\/15-minute-apps\/\">15 minute apps<\/a>. The updated source code is available to download below.<\/p>\n<p><a href=\"https:\/\/www.pythonguis.com\/d\/moonsweeper-fbs-pyqt5.zip\">Moonsweeper Source (fbs) PyQt5<\/a><\/p>\n<p><a href=\"https:\/\/www.pythonguis.com\/d\/moonsweeper-fbs-pyside2.zip\">Moonsweeper Source (fbs) PySide2<\/a><\/p>\n<p class=\"admonition admonition-note\"><i class=\"fas fa-pencil-alt\"><\/i> Only the changes required to convert <em>Moonsweeper<\/em> over to <strong>fbs<\/strong> are covered here. If you want to see how_ Moonsweeper_ itself works, see the original App article. The custom application icons were created using icon art by <a href=\"https:\/\/www.flaticon.com\/authors\/freepik\/\">Freepik<\/a>.<\/p>\n<p>The project follows the same basic structure as for the stub application we created above.<\/p>\n<div class=\"code-block\"><span class=\"code-block-language code-block-python\">python<\/span><\/p>\n<pre><code class=\"\" data-line=\"\">.\n\u251c\u2500\u2500 README.md\n\u251c\u2500\u2500 requirements.txt\n\u251c\u2500\u2500 screenshot-minesweeper1.jpg\n\u251c\u2500\u2500 screenshot-minesweeper2.jpg\n\u2514\u2500\u2500 src\n    \u251c\u2500\u2500 build\n    \u2502\u00a0\u00a0 \u2514\u2500\u2500 settings\n    \u2502\u00a0\u00a0     \u251c\u2500\u2500 base.json\n    \u2502\u00a0\u00a0     \u251c\u2500\u2500 linux.json\n    \u2502\u00a0\u00a0     \u2514\u2500\u2500 mac.json\n    \u2514\u2500\u2500 main\n        \u251c\u2500\u2500 Installer.nsi\n        \u251c\u2500\u2500 icons\n        \u2502\u00a0\u00a0 \u251c\u2500\u2500 Icon.ico\n        \u2502\u00a0\u00a0 \u251c\u2500\u2500 README.md\n        \u2502\u00a0\u00a0 \u251c\u2500\u2500 base\n        \u2502\u00a0\u00a0 \u2502\u00a0\u00a0 \u251c\u2500\u2500 &lt;span class=&quot;hljs-number&quot;&gt;16.&lt;\/span&gt;png\n        \u2502\u00a0\u00a0 \u2502\u00a0\u00a0 \u251c\u2500\u2500 &lt;span class=&quot;hljs-number&quot;&gt;24.&lt;\/span&gt;png\n        \u2502\u00a0\u00a0 \u2502\u00a0\u00a0 \u251c\u2500\u2500 &lt;span class=&quot;hljs-number&quot;&gt;32.&lt;\/span&gt;png\n        \u2502\u00a0\u00a0 \u2502\u00a0\u00a0 \u251c\u2500\u2500 &lt;span class=&quot;hljs-number&quot;&gt;48.&lt;\/span&gt;png\n        \u2502\u00a0\u00a0 \u2502\u00a0\u00a0 \u2514\u2500\u2500 &lt;span class=&quot;hljs-number&quot;&gt;64.&lt;\/span&gt;png\n        \u2502\u00a0\u00a0 \u251c\u2500\u2500 linux\n        \u2502\u00a0\u00a0 \u2502\u00a0\u00a0 \u251c\u2500\u2500 &lt;span class=&quot;hljs-number&quot;&gt;1024.&lt;\/span&gt;png\n        \u2502\u00a0\u00a0 \u2502\u00a0\u00a0 \u251c\u2500\u2500 &lt;span class=&quot;hljs-number&quot;&gt;128.&lt;\/span&gt;png\n        \u2502\u00a0\u00a0 \u2502\u00a0\u00a0 \u251c\u2500\u2500 &lt;span class=&quot;hljs-number&quot;&gt;256.&lt;\/span&gt;png\n        \u2502\u00a0\u00a0 \u2502\u00a0\u00a0 \u2514\u2500\u2500 &lt;span class=&quot;hljs-number&quot;&gt;512.&lt;\/span&gt;png\n        \u2502\u00a0\u00a0 \u2514\u2500\u2500 mac\n        \u2502\u00a0\u00a0     \u251c\u2500\u2500 &lt;span class=&quot;hljs-number&quot;&gt;1024.&lt;\/span&gt;png\n        \u2502\u00a0\u00a0     \u251c\u2500\u2500 &lt;span class=&quot;hljs-number&quot;&gt;128.&lt;\/span&gt;png\n        \u2502\u00a0\u00a0     \u251c\u2500\u2500 &lt;span class=&quot;hljs-number&quot;&gt;256.&lt;\/span&gt;png\n        \u2502\u00a0\u00a0     \u2514\u2500\u2500 &lt;span class=&quot;hljs-number&quot;&gt;512.&lt;\/span&gt;png\n        \u251c\u2500\u2500 python\n        \u2502\u00a0\u00a0 \u251c\u2500\u2500 __init__.py\n        \u2502\u00a0\u00a0 \u2514\u2500\u2500 main.py\n        \u2514\u2500\u2500 resources\n            \u251c\u2500\u2500 base\n            \u2502\u00a0\u00a0 \u2514\u2500\u2500 images\n            \u2502\u00a0\u00a0     \u251c\u2500\u2500 bomb.png\n            \u2502\u00a0\u00a0     \u251c\u2500\u2500 bug.png\n            \u2502\u00a0\u00a0     \u251c\u2500\u2500 clock-select.png\n            \u2502\u00a0\u00a0     \u251c\u2500\u2500 cross.png\n            \u2502\u00a0\u00a0     \u251c\u2500\u2500 flag.png\n            \u2502\u00a0\u00a0     \u251c\u2500\u2500 plus.png\n            \u2502\u00a0\u00a0     \u251c\u2500\u2500 rocket.png\n            \u2502\u00a0\u00a0     \u251c\u2500\u2500 smiley-lol.png\n            \u2502\u00a0\u00a0     \u2514\u2500\u2500 smiley.png\n            \u2514\u2500\u2500 mac\n                \u2514\u2500\u2500 Contents\n                    \u2514\u2500\u2500 Info.plist\n<\/code><\/pre>\n<\/div>\n<p>The <code class=\"\" data-line=\"\">src\/build\/settings\/base.json<\/code> stores the basic details about the application, including the entry point to run the app with <code class=\"\" data-line=\"\">fbs run<\/code> or once packaged.<\/p>\n<div class=\"code-block\"><span class=\"code-block-language code-block-json\">json<\/span><\/p>\n<pre><code class=\"\" data-line=\"\">{\n    &lt;span class=&quot;hljs-attr&quot;&gt;&quot;app_name&quot;&lt;\/span&gt;: &lt;span class=&quot;hljs-string&quot;&gt;&quot;Moonsweeper&quot;&lt;\/span&gt;,\n    &lt;span class=&quot;hljs-attr&quot;&gt;&quot;author&quot;&lt;\/span&gt;: &lt;span class=&quot;hljs-string&quot;&gt;&quot;Martin Fitzpatrick&quot;&lt;\/span&gt;,\n    &lt;span class=&quot;hljs-attr&quot;&gt;&quot;main_module&quot;&lt;\/span&gt;: &lt;span class=&quot;hljs-string&quot;&gt;&quot;src\/main\/python\/main.py&quot;&lt;\/span&gt;,\n    &lt;span class=&quot;hljs-attr&quot;&gt;&quot;version&quot;&lt;\/span&gt;: &lt;span class=&quot;hljs-string&quot;&gt;&quot;0.0.0&quot;&lt;\/span&gt;\n}\n<\/code><\/pre>\n<\/div>\n<p>The script <em>entry point<\/em> is at the base of <code class=\"\" data-line=\"\">src\/main\/python\/main.py<\/code>. This creates the <code class=\"\" data-line=\"\">AppContext<\/code> object and calls the <code class=\"\" data-line=\"\">.run()<\/code> method to start up the app.<\/p>\n<div class=\"code-block\"><span class=\"code-block-language code-block-python\">python<\/span><\/p>\n<pre><code class=\"\" data-line=\"\">&lt;span class=&quot;hljs-keyword&quot;&gt;if&lt;\/span&gt; __name__ == &lt;span class=&quot;hljs-string&quot;&gt;&#039;__main__&#039;&lt;\/span&gt;:\n    appctxt = AppContext()\n    exit_code = appctxt.run()\n    sys.exit(exit_code)\n<\/code><\/pre>\n<\/div>\n<p>The <code class=\"\" data-line=\"\">ApplicationContext<\/code> defines a <code class=\"\" data-line=\"\">.run()<\/code> method to handle initialisation. In this case that consists of creating and showing the main window, then starting up the event loop.<\/p>\n<div class=\"code-block\"><span class=\"code-block-language code-block-python\">python<\/span><\/p>\n<pre><code class=\"\" data-line=\"\">&lt;span class=&quot;hljs-keyword&quot;&gt;from&lt;\/span&gt; fbs_runtime.application_context.PyQt5 &lt;span class=&quot;hljs-keyword&quot;&gt;import&lt;\/span&gt; ApplicationContext, \\\n    cached_property\n\n\n&lt;span class=&quot;hljs-class&quot;&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;class&lt;\/span&gt; &lt;span class=&quot;hljs-title&quot;&gt;AppContext&lt;\/span&gt;(&lt;span class=&quot;hljs-params&quot;&gt;ApplicationContext&lt;\/span&gt;):&lt;\/span&gt;\n    &lt;span class=&quot;hljs-function&quot;&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;def&lt;\/span&gt; &lt;span class=&quot;hljs-title&quot;&gt;run&lt;\/span&gt;(&lt;span class=&quot;hljs-params&quot;&gt;self&lt;\/span&gt;):&lt;\/span&gt;\n        self.main_window.show()\n        &lt;span class=&quot;hljs-keyword&quot;&gt;return&lt;\/span&gt; self.app.exec_()\n\n&lt;span class=&quot;hljs-meta&quot;&gt;    @cached_property&lt;\/span&gt;\n    &lt;span class=&quot;hljs-function&quot;&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;def&lt;\/span&gt; &lt;span class=&quot;hljs-title&quot;&gt;main_window&lt;\/span&gt;(&lt;span class=&quot;hljs-params&quot;&gt;self&lt;\/span&gt;):&lt;\/span&gt;\n        &lt;span class=&quot;hljs-keyword&quot;&gt;return&lt;\/span&gt; MainWindow(self)  &lt;span class=&quot;hljs-comment&quot;&gt;# Pass context to the window.&lt;\/span&gt;\n\n    &lt;span class=&quot;hljs-comment&quot;&gt;# ... snip ...&lt;\/span&gt;\n<\/code><\/pre>\n<\/div>\n<h3>The <code class=\"\" data-line=\"\">cached_property<\/code> decorator<\/h3>\n<p>The <code class=\"\" data-line=\"\">.run()<\/code> method accesses <code class=\"\" data-line=\"\">self.main_window<\/code>. You\u2019ll notice that this method is wrapped in an <strong>fbs<\/strong> <code class=\"\" data-line=\"\">@cached_property<\/code> decorator. This decorator turns the method into a property (like the Python <code class=\"\" data-line=\"\">@property<\/code> decorator) and caches the return value.<\/p>\n<p>The first time the property is accessed the method is executed and the return value cached. On subsequent calls, the cached value is returned directly without executing anything. This also has the side-effect of postponing creation of these objects until they are needed.<\/p>\n<p>You can use <code class=\"\" data-line=\"\">@cached_property<\/code> to define each application component (a window, a toolbar, a database connection or other resources). However, you don\u2019t <em>have<\/em> to use the <code class=\"\" data-line=\"\">@cached_property<\/code> \u2014 you could alternatively declare all properties in your <code class=\"\" data-line=\"\">ApplicationContext.__init__<\/code> block as shown below.<\/p>\n<div class=\"code-block\"><span class=\"code-block-language code-block-python\">python<\/span><\/p>\n<pre><code class=\"\" data-line=\"\">&lt;span class=&quot;hljs-keyword&quot;&gt;from&lt;\/span&gt; fbs_runtime.application_context.PyQt5 &lt;span class=&quot;hljs-keyword&quot;&gt;import&lt;\/span&gt; ApplicationContext\n\n&lt;span class=&quot;hljs-class&quot;&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;class&lt;\/span&gt; &lt;span class=&quot;hljs-title&quot;&gt;AppContext&lt;\/span&gt;(&lt;span class=&quot;hljs-params&quot;&gt;ApplicationContext&lt;\/span&gt;):&lt;\/span&gt;\n\n    &lt;span class=&quot;hljs-function&quot;&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;def&lt;\/span&gt; &lt;span class=&quot;hljs-title&quot;&gt;__init__&lt;\/span&gt;(&lt;span class=&quot;hljs-params&quot;&gt;self, *args, **kwargs&lt;\/span&gt;):&lt;\/span&gt;\n        &lt;span class=&quot;hljs-built_in&quot;&gt;super&lt;\/span&gt;(AppContext, self).__init__(*args, **kwargs)\n\n        self.window = Window()\n\n    &lt;span class=&quot;hljs-function&quot;&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;def&lt;\/span&gt; &lt;span class=&quot;hljs-title&quot;&gt;run&lt;\/span&gt;(&lt;span class=&quot;hljs-params&quot;&gt;self&lt;\/span&gt;):&lt;\/span&gt;\n        self.window.show()\n        &lt;span class=&quot;hljs-keyword&quot;&gt;return&lt;\/span&gt; self.app.exec_()\n\n<\/code><\/pre>\n<\/div>\n<h3>Accessing resources with <code class=\"\" data-line=\"\">.get_resource<\/code><\/h3>\n<p>Applications usually require additional data files beyond the source code \u2014\u00a0for example files icons, images, styles (Qt\u2019s <code class=\"\" data-line=\"\">.qss<\/code> files) or documentation. You may also want to bundle platform-specific libraries or binaries. To simplify this <strong>fbs<\/strong> defines a folder structure and access method which work seamlessly across development and distributed versions.<\/p>\n<p>The top level folder <code class=\"\" data-line=\"\">resources\/<\/code> should contain a folder <code class=\"\" data-line=\"\">base<\/code> plus any combination of the other folders shown below. The <code class=\"\" data-line=\"\">base<\/code> folder contains files common to all platforms, while the platform-specific folders can be used for any files specific to a given OS.<\/p>\n<div class=\"code-block\"><span class=\"code-block-language code-block-bash\">bash<\/span><\/p>\n<pre><code class=\"\" data-line=\"\">base\/           &lt;span class=&quot;hljs-comment&quot;&gt;# for files required on all OSs&lt;\/span&gt;\nwindows\/        &lt;span class=&quot;hljs-comment&quot;&gt;# for files only required on Windows&lt;\/span&gt;\nmac\/            &lt;span class=&quot;hljs-comment&quot;&gt;# &quot;  &quot;      &quot;    &quot;        &quot;  Mac&lt;\/span&gt;\nlinux\/          &lt;span class=&quot;hljs-comment&quot;&gt;# &quot;  &quot;      &quot;    &quot;        &quot;  Linux&lt;\/span&gt;\narch\/           &lt;span class=&quot;hljs-comment&quot;&gt;# &quot;  &quot;      &quot;    &quot;        &quot;  Arch Linux&lt;\/span&gt;\nfedora\/         &lt;span class=&quot;hljs-comment&quot;&gt;# &quot;  &quot;      &quot;    &quot;        &quot;  Debian Linux&lt;\/span&gt;\nubuntu\/         &lt;span class=&quot;hljs-comment&quot;&gt;# &quot;  &quot;      &quot;    &quot;        &quot;  Ubuntu Linux&lt;\/span&gt;\n<\/code><\/pre>\n<\/div>\n<p class=\"admonition admonition-note\"><i class=\"fas fa-pencil-alt\"><\/i> Getting files into the right place to load from a distributed app across all platforms is usually one of the faffiest bits of distributing PyQt applications. It\u2019s really handy that <strong>fbs<\/strong> handles this for you.<\/p>\n<p>To simplify the loading of resources from your <code class=\"\" data-line=\"\">resources\/<\/code> folder in your applications <strong>fbs<\/strong> provides the <code class=\"\" data-line=\"\">ApplicationContext.get_resource()<\/code> method. This method takes the name of a file which can be found somewhere in the <code class=\"\" data-line=\"\">resources\/<\/code> folder and returns the absolute path to that file. You can use this returned absolute path to open the file as normal.<\/p>\n<div class=\"code-block\"><span class=\"code-block-language code-block-python\">python<\/span><\/p>\n<pre><code class=\"\" data-line=\"\">&lt;span class=&quot;hljs-keyword&quot;&gt;from&lt;\/span&gt; fbs_runtime.application_context.PyQt5 &lt;span class=&quot;hljs-keyword&quot;&gt;import&lt;\/span&gt; ApplicationContext,     cached_property\n\n\n&lt;span class=&quot;hljs-class&quot;&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;class&lt;\/span&gt; &lt;span class=&quot;hljs-title&quot;&gt;AppContext&lt;\/span&gt;(&lt;span class=&quot;hljs-params&quot;&gt;ApplicationContext&lt;\/span&gt;):&lt;\/span&gt;\n\n    &lt;span class=&quot;hljs-comment&quot;&gt;# ... snip ...&lt;\/span&gt;\n\n&lt;span class=&quot;hljs-meta&quot;&gt;    @cached_property&lt;\/span&gt;\n    &lt;span class=&quot;hljs-function&quot;&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;def&lt;\/span&gt; &lt;span class=&quot;hljs-title&quot;&gt;img_bomb&lt;\/span&gt;(&lt;span class=&quot;hljs-params&quot;&gt;self&lt;\/span&gt;):&lt;\/span&gt;\n        &lt;span class=&quot;hljs-keyword&quot;&gt;return&lt;\/span&gt; QImage(self.get_resource(&lt;span class=&quot;hljs-string&quot;&gt;&#039;images\/bug.png&#039;&lt;\/span&gt;))\n\n&lt;span class=&quot;hljs-meta&quot;&gt;    @cached_property&lt;\/span&gt;\n    &lt;span class=&quot;hljs-function&quot;&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;def&lt;\/span&gt; &lt;span class=&quot;hljs-title&quot;&gt;img_flag&lt;\/span&gt;(&lt;span class=&quot;hljs-params&quot;&gt;self&lt;\/span&gt;):&lt;\/span&gt;\n        &lt;span class=&quot;hljs-keyword&quot;&gt;return&lt;\/span&gt; QImage(self.get_resource(&lt;span class=&quot;hljs-string&quot;&gt;&#039;images\/flag.png&#039;&lt;\/span&gt;))\n\n&lt;span class=&quot;hljs-meta&quot;&gt;    @cached_property&lt;\/span&gt;\n    &lt;span class=&quot;hljs-function&quot;&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;def&lt;\/span&gt; &lt;span class=&quot;hljs-title&quot;&gt;img_start&lt;\/span&gt;(&lt;span class=&quot;hljs-params&quot;&gt;self&lt;\/span&gt;):&lt;\/span&gt;\n        &lt;span class=&quot;hljs-keyword&quot;&gt;return&lt;\/span&gt; QImage(self.get_resource(&lt;span class=&quot;hljs-string&quot;&gt;&#039;images\/rocket.png&#039;&lt;\/span&gt;))\n\n&lt;span class=&quot;hljs-meta&quot;&gt;    @cached_property&lt;\/span&gt;\n    &lt;span class=&quot;hljs-function&quot;&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;def&lt;\/span&gt; &lt;span class=&quot;hljs-title&quot;&gt;img_clock&lt;\/span&gt;(&lt;span class=&quot;hljs-params&quot;&gt;self&lt;\/span&gt;):&lt;\/span&gt;\n        &lt;span class=&quot;hljs-keyword&quot;&gt;return&lt;\/span&gt; QImage(self.get_resource(&lt;span class=&quot;hljs-string&quot;&gt;&#039;images\/clock-select.png&#039;&lt;\/span&gt;))\n\n&lt;span class=&quot;hljs-meta&quot;&gt;    @cached_property&lt;\/span&gt;\n    &lt;span class=&quot;hljs-function&quot;&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;def&lt;\/span&gt; &lt;span class=&quot;hljs-title&quot;&gt;status_icons&lt;\/span&gt;(&lt;span class=&quot;hljs-params&quot;&gt;self&lt;\/span&gt;):&lt;\/span&gt;\n        &lt;span class=&quot;hljs-keyword&quot;&gt;return&lt;\/span&gt; {\n            STATUS_READY: QIcon(self.get_resource(&lt;span class=&quot;hljs-string&quot;&gt;&quot;images\/plus.png&quot;&lt;\/span&gt;)),\n            STATUS_PLAYING: QIcon(self.get_resource(&lt;span class=&quot;hljs-string&quot;&gt;&quot;images\/smiley.png&quot;&lt;\/span&gt;)),\n            STATUS_FAILED: QIcon(self.get_resource(&lt;span class=&quot;hljs-string&quot;&gt;&quot;images\/cross.png&quot;&lt;\/span&gt;)),\n            STATUS_SUCCESS: QIcon(self.get_resource(&lt;span class=&quot;hljs-string&quot;&gt;&quot;images\/smiley-lol.png&quot;&lt;\/span&gt;))\n        }\n\n    &lt;span class=&quot;hljs-comment&quot;&gt;# ... snip ...&lt;\/span&gt;\n<\/code><\/pre>\n<\/div>\n<p>In our <em>Moonsweeper<\/em> application above, we have a <em>bomb<\/em> image file available at <code class=\"\" data-line=\"\">src\/main\/resources\/base\/images\/bug.jpg<\/code>. By calling <code class=\"\" data-line=\"\">ctx.get_resource(&#039;images\/bug.png&#039;)<\/code> we get the absolute path to that image file on the filesystem, allowing us to open the file within our app.<\/p>\n<p class=\"admonition admonition-note\"><i class=\"fas fa-pencil-alt\"><\/i> If the file does not exist `FileNotFoundError` will be raised instead.<\/p>\n<p>The handy thing about this method is that it transparently handles the platform folders under <code class=\"\" data-line=\"\">src\/main\/resources<\/code> giving OS-specific files precedence. For example, if the same file was also present under <code class=\"\" data-line=\"\">src\/main\/resources\/mac\/images\/bug.jpg<\/code> and we called <code class=\"\" data-line=\"\">ctx.get_resource(&#039;images\/bug.jpg&#039;)<\/code> we would get the Mac version of the file.<\/p>\n<p>Additionally <code class=\"\" data-line=\"\">get_resource<\/code> works both when running from source and when running a frozen or installed version of your application. If your <code class=\"\" data-line=\"\">resources\/<\/code> load correctly locally you can be confident they will load correctly in your distributed applications.<\/p>\n<h3>Using the <code class=\"\" data-line=\"\">ApplicationContext<\/code> from app<\/h3>\n<p>As shown above, our <code class=\"\" data-line=\"\">ApplicationContext<\/code> object has cached properties to load and return the resources. To allow us to access these from our <code class=\"\" data-line=\"\">QMainWindow<\/code> we can pass the context in and store a reference to it in our window <code class=\"\" data-line=\"\">__init__<\/code>.<\/p>\n<div class=\"code-block\"><span class=\"code-block-language code-block-python\">python<\/span><\/p>\n<pre><code class=\"\" data-line=\"\">&lt;span class=&quot;hljs-class&quot;&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;class&lt;\/span&gt; &lt;span class=&quot;hljs-title&quot;&gt;MainWindow&lt;\/span&gt;(&lt;span class=&quot;hljs-params&quot;&gt;QMainWindow&lt;\/span&gt;):&lt;\/span&gt;\n    &lt;span class=&quot;hljs-function&quot;&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;def&lt;\/span&gt; &lt;span class=&quot;hljs-title&quot;&gt;__init__&lt;\/span&gt;(&lt;span class=&quot;hljs-params&quot;&gt;self, ctx&lt;\/span&gt;):&lt;\/span&gt;\n        &lt;span class=&quot;hljs-built_in&quot;&gt;super&lt;\/span&gt;(MainWindow, self).__init__()\n\n        self.ctx = ctx  &lt;span class=&quot;hljs-comment&quot;&gt;#\u00a0Store a reference to the context for resources, etc.&lt;\/span&gt;\n\n&lt;span class=&quot;hljs-comment&quot;&gt;# ... snip ...&lt;\/span&gt;\n<\/code><\/pre>\n<\/div>\n<p>Now that we have access to the context via <code class=\"\" data-line=\"\">self.ctx<\/code> we can use it this in any place we want to reference these external resources.<\/p>\n<div class=\"code-block\"><span class=\"code-block-language code-block-python\">python<\/span><\/p>\n<pre><code class=\"\" data-line=\"\">        l = QLabel()\n        l.setPixmap(QPixmap.fromImage(self.ctx.img_bomb))\n        l.setAlignment(Qt.AlignRight | Qt.AlignVCenter)\n        hb.addWidget(l)\n\n&lt;span class=&quot;hljs-comment&quot;&gt;# ... snip ...&lt;\/span&gt;\n\n        l = QLabel()\n        l.setPixmap(QPixmap.fromImage(self.ctx.img_clock))\n        l.setAlignment(Qt.AlignLeft | Qt.AlignVCenter)\n        hb.addWidget(l)\n\n<\/code><\/pre>\n<\/div>\n<p>The first time we access <code class=\"\" data-line=\"\">self.ctx.img_bomb<\/code> the file will be loaded, the <code class=\"\" data-line=\"\">QImage<\/code> created and returned. On subsequent calls, we\u2019ll get the image from the cache.<\/p>\n<div class=\"code-block\"><span class=\"code-block-language code-block-python\">python<\/span><\/p>\n<pre><code class=\"\" data-line=\"\">    &lt;span class=&quot;hljs-function&quot;&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;def&lt;\/span&gt; &lt;span class=&quot;hljs-title&quot;&gt;init_map&lt;\/span&gt;(&lt;span class=&quot;hljs-params&quot;&gt;self&lt;\/span&gt;):&lt;\/span&gt;\n        &lt;span class=&quot;hljs-comment&quot;&gt;# Add positions to the map&lt;\/span&gt;\n        &lt;span class=&quot;hljs-keyword&quot;&gt;for&lt;\/span&gt; x &lt;span class=&quot;hljs-keyword&quot;&gt;in&lt;\/span&gt; &lt;span class=&quot;hljs-built_in&quot;&gt;range&lt;\/span&gt;(&lt;span class=&quot;hljs-number&quot;&gt;0&lt;\/span&gt;, self.b_size):\n            &lt;span class=&quot;hljs-keyword&quot;&gt;for&lt;\/span&gt; y &lt;span class=&quot;hljs-keyword&quot;&gt;in&lt;\/span&gt; &lt;span class=&quot;hljs-built_in&quot;&gt;range&lt;\/span&gt;(&lt;span class=&quot;hljs-number&quot;&gt;0&lt;\/span&gt;, self.b_size):\n                w = Pos(x, y, self.ctx.img_flag, self.ctx.img_start, self.ctx.img_bomb)\n                self.grid.addWidget(w, y, x)\n                &lt;span class=&quot;hljs-comment&quot;&gt;# Connect signal to handle expansion.&lt;\/span&gt;\n                w.clicked.connect(self.trigger_start)\n                w.expandable.connect(self.expand_reveal)\n                w.ohno.connect(self.game_over)\n\n&lt;span class=&quot;hljs-comment&quot;&gt;# ... snip ...&lt;\/span&gt;\n\n        self.button.setIcon(self.ctx.status_icons[STATUS_PLAYING])\n\n&lt;span class=&quot;hljs-comment&quot;&gt;# ... snip ...&lt;\/span&gt;\n\n    &lt;span class=&quot;hljs-function&quot;&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;def&lt;\/span&gt; &lt;span class=&quot;hljs-title&quot;&gt;update_status&lt;\/span&gt;(&lt;span class=&quot;hljs-params&quot;&gt;self, status&lt;\/span&gt;):&lt;\/span&gt;\n        self.status = status\n        self.button.setIcon(self.ctx.status_icons[self.status])\n\n<\/code><\/pre>\n<\/div>\n<p>Those are all the changes needed to get the <em>Moonsweeper<\/em> app packageable with <strong>fbs<\/strong>. If you open up the source folder you should be able to start it up as before.<\/p>\n<div class=\"code-block\"><span class=\"code-block-language code-block-bash\">bash<\/span><\/p>\n<pre><code class=\"\" data-line=\"\">fbs run\n<\/code><\/pre>\n<\/div>\n<p>If that\u2019s working, you\u2019re ready to move onto freezing and building in the installer.<\/p>\n<h2 id=\"freezing-the-app\">Freezing the app<\/h2>\n<p><em>Freezing<\/em> is the process of turning a Python application into a standalone executable that can run on another user\u2019s computer. Use the following command to turn the app&#8217;s source code into a standalone executable:<\/p>\n<div class=\"code-block\"><span class=\"code-block-language code-block-python\">python<\/span><\/p>\n<pre><code class=\"\" data-line=\"\">fbs freeze\n<\/code><\/pre>\n<\/div>\n<p>The resulting executable depends on the platform you <em>freeze<\/em> on \u2014\u00a0the executable will only work on the OS you built it on (e.g. an executable built on Windows will run on another Windows computer, but not on a Mac).<\/p>\n<ul>\n<li>Windows will create an <code class=\"\" data-line=\"\">.exe<\/code> executable in the folder <code class=\"\" data-line=\"\">target\/&lt;AppName&gt;<\/code><\/li>\n<li>MacOS X will create an <code class=\"\" data-line=\"\">.app<\/code> application bundle in <code class=\"\" data-line=\"\">target\/&lt;AppName&gt;.app<\/code><\/li>\n<li>Linux will create an executable in the folder <code class=\"\" data-line=\"\">target\/&lt;AppName&gt;<\/code><\/li>\n<\/ul>\n<p class=\"admonition admonition-note\"><i class=\"fas fa-pencil-alt\"><\/i> On Windows you may need to install the <a href=\"https:\/\/dev.windows.com\/en-us\/downloads\/windows-10-sdk\">Windows 10 SDK<\/a>, although <strong>fbs<\/strong> will prompt you if this is the case.<\/p>\n<div class=\"product pure-g\">\n<div class=\"pure-u-1-1 pure-u-md-1-2 l-box\">\n<h3><a href=\"https:\/\/www.pythonguis.com\/pyqt6-book\/\">Create GUI Applications with Python &amp; Qt6<small><br \/>\nThe easy way to create desktop applications<\/small><\/a><\/h3>\n<div class=\"product-details\">\n<p>My complete guide, updated for 2021 &amp; PyQt6. Everything you need build real apps.<\/p>\n<\/div>\n<div class=\"product-actions\"><\/div>\n<\/div>\n<\/div>\n<\/div>\n<\/article>\n<\/div>\n<\/div>\n<div id=\"article-main-body\" class=\"article-wrap  article-main-body\">\n<div class=\"product pure-g\">\n<div class=\"pure-u-1-1 pure-u-md-1-2\"><a href=\"https:\/\/www.pythonguis.com\/pyqt6-book\/\"><img data-recalc-dims=\"1\" decoding=\"async\" class=\"pure-img product-image\" title=\"PyQt6 book\" src=\"https:\/\/i0.wp.com\/monodes.com\/predaelli\/wp-content\/uploads\/sites\/4\/2021\/12\/pyqt6.png?w=910&#038;ssl=1\" \/><\/a><\/p>\n<div class=\"pure-u-1-1 product-smallprint\">\n<p>Downloadable ebook (PDF, ePub) &amp; Complete Source code<\/p>\n<\/div>\n<\/div>\n<div class=\"pure-u-1-1 product-coupon\">\n<p class=\"sale-notice\"><strong>To support developers in Italy<\/strong> I give a <strong><i class=\"fas fa-gift\"><\/i> 50% discount<\/strong> with the code WE5GY0 \u2014 Enjoy!<\/p>\n<\/div>\n<\/div>\n<h2 id=\"creating-the-installer\">Creating the Installer<\/h2>\n<p>While you can share the executable files with users, desktop applications are normally distributed with <em>installers<\/em> which handle the process of putting the executable (and any other files) in the correct place. See the following sections for platform-specific notes before creating<\/p>\n<p class=\"admonition admonition-note\"><i class=\"fas fa-pencil-alt\"><\/i> You must <em>freeze<\/em> your app first <em>then<\/em> create the installer.<\/p>\n<h2 id=\"windows-installer\">Windows installer<\/h2>\n<p>The Windows installer allows your users to pick the installation directory for the executable and adds your app to the user\u2019s Start Menu. The app is also added to installed programs, allowing it to be uninstalled by your users.<\/p>\n<p>Before you create installers on Windows you will need to install <a href=\"http:\/\/nsis.sourceforge.net\/Main_Page\">NSIS<\/a> and ensure its installation directory is in your <code class=\"\" data-line=\"\">PATH<\/code>. You can then build an installer using \u2014<\/p>\n<div class=\"code-block\"><span class=\"code-block-language code-block-bash\">bash<\/span><\/p>\n<pre><code class=\"\" data-line=\"\">fbs installer\n<\/code><\/pre>\n<\/div>\n<p>The Windows installer will be created at <code class=\"\" data-line=\"\">target\/&lt;AppName&gt;Setup.exe<\/code>.<\/p>\n<p><a href=\"https:\/\/www.pythonguis.com\/d\/MoonsweeperSetup_baG7uxK.exe\">Moonsweeper Windows Installer<\/a><\/p>\n<p><img data-recalc-dims=\"1\" decoding=\"async\" src=\"https:\/\/i0.wp.com\/monodes.com\/predaelli\/wp-content\/uploads\/sites\/4\/2021\/12\/moonsweeper-installer-windows.png?w=910&#038;ssl=1\" alt=\"Moonsweeper Windows NSIS installer\" \/> <em>Moonsweeper Windows NSIS installer<\/em><\/p>\n<h2 id=\"mac-installer\">Mac installer<\/h2>\n<p>There are no additional steps to create a MacOS installer. Just run the <strong>fbs<\/strong> command \u2014<\/p>\n<div class=\"code-block\"><span class=\"code-block-language code-block-bash\">bash<\/span><\/p>\n<pre><code class=\"\" data-line=\"\">fbs installer\n<\/code><\/pre>\n<\/div>\n<p>On Mac the command will generate a disk image at <code class=\"\" data-line=\"\">target\/&lt;AppName&gt;.dmg<\/code>. This disk image will contain the app bundle and a shortcut to the Applications folder. When your users open it they can drag the app to the Applications folder to install it.<\/p>\n<p><a href=\"http:\/\/download.mfitzp.com\/Moonsweeper.dmg\">Download the <em>Moonsweeper<\/em> .dmg bundle here<\/a><\/p>\n<p><a href=\"https:\/\/www.pythonguis.com\/d\/Moonsweeper_rbQAuYf.dmg\">Moonsweeper macOS Installer<\/a><\/p>\n<p><img data-recalc-dims=\"1\" decoding=\"async\" src=\"https:\/\/i0.wp.com\/monodes.com\/predaelli\/wp-content\/uploads\/sites\/4\/2021\/12\/moonsweeper-installer-mac_JGHs5Ez.png?w=910&#038;ssl=1\" alt=\"Moonsweeper Mac Disk Image\" \/> <em>Moonsweeper Mac Disk Image<\/em><\/p>\n<h2 id=\"linux-installer\">Linux installer<\/h2>\n<p>To build installers on Linux you need to install the Ruby tool <a href=\"https:\/\/github.com\/jordansissel\/fpm\">Effing package management!<\/a> \u2014 use <a href=\"https:\/\/fpm.readthedocs.io\/en\/latest\/installing.html\">the installation guide<\/a> to get it set up. Once that is in place you can use the standard command to create the Linux package file.<\/p>\n<div class=\"code-block\"><span class=\"code-block-language code-block-bash\">bash<\/span><\/p>\n<pre><code class=\"\" data-line=\"\">fbs installer\n<\/code><\/pre>\n<\/div>\n<p>The resulting package will be created under the <code class=\"\" data-line=\"\">target\/<\/code> folder. Depending on your platform the package file will be named <code class=\"\" data-line=\"\">&lt;AppName&gt;.deb<\/code>, <code class=\"\" data-line=\"\">&lt;AppName&gt;.pkg.tar.xz<\/code> or <code class=\"\" data-line=\"\">&lt;AppName&gt;.rpm<\/code>. Your users can install this file with their package manager.<\/p>\n<p><a href=\"https:\/\/www.pythonguis.com\/d\/Moonsweeper_vZmFhBa.deb\">Moonsweeper Ubuntu Installer<\/a><\/p>\n<\/div>\n<div class=\"pure-g controls-container\">\n<div class=\"pure-button-group pure-u-1-1 center\" role=\"group\"><\/div>\n<\/div>\n<div class=\"pure-u-1-1 pure-u-md-17-24 l-box\">\n<article class=\"post\">\n<div class=\"pure-g controls-container\">\n<div class=\"pure-u-1-1\"><\/div>\n<\/div>\n<\/article>\n<\/div>\n<div id=\"sidebar\" class=\"pure-u-1-1 pure-u-md-7-24 l-box sidebar sidebar-right\">\n<div class=\"pure-g progress-container\"><\/div>\n<\/div>\n<div class=\"pure-u-1-2 progress-middle\">\n<div class=\"progress-text\">2\/2<\/div>\n<div class=\"progress-bar\"><\/div>\n<\/div>\n<div class=\"pure-u-1-1\"><\/div>\n<div id=\"container\" class=\"pure-g\">\n<div id=\"sidebar\" class=\"pure-u-1-1 pure-u-md-7-24 l-box sidebar sidebar-right\">\n<h4>Contents<\/h4>\n<div id=\"toc\">\n<ul>\n<li><a class=\"toc-href\" href=\"https:\/\/www.pythonguis.com\/tutorials\/packaging-pyqt5-apps-fbs\/#install-requirements\">Install requirements<\/a><\/li>\n<li><a class=\"toc-href\" href=\"https:\/\/www.pythonguis.com\/tutorials\/packaging-pyqt5-apps-fbs\/#starting-an-app\">Starting an app<\/a><\/li>\n<li><a class=\"toc-href\" href=\"https:\/\/www.pythonguis.com\/tutorials\/packaging-pyqt5-apps-fbs\/#building-a-real-application\">Building a real application<\/a><\/li>\n<li><a class=\"toc-href\" href=\"https:\/\/www.pythonguis.com\/tutorials\/packaging-pyqt5-apps-fbs\/#freezing-the-app\">Freezing the app<\/a><\/li>\n<li><a class=\"toc-href\" href=\"https:\/\/www.pythonguis.com\/tutorials\/packaging-pyqt5-apps-fbs\/#creating-the-installer\">Creating the Installer<\/a><\/li>\n<li><a class=\"toc-href\" href=\"https:\/\/www.pythonguis.com\/tutorials\/packaging-pyqt5-apps-fbs\/#windows-installer\">Windows installer<\/a><\/li>\n<li><a class=\"toc-href\" href=\"https:\/\/www.pythonguis.com\/tutorials\/packaging-pyqt5-apps-fbs\/#mac-installer\">Mac installer<\/a><\/li>\n<li><a class=\"toc-href\" href=\"https:\/\/www.pythonguis.com\/tutorials\/packaging-pyqt5-apps-fbs\/#linux-installer\">Linux installer<\/a><\/li>\n<\/ul>\n<\/div>\n<h4>Share<\/h4>\n<ul class=\"post-share fa-ul\">\n<li><i class=\"fab fa-twitter\"><\/i> <a class=\"twitter\" href=\"https:\/\/twitter.com\/share?text=Packaging PyQt5 apps with fbs&amp;url=https:\/\/www.pythonguis.com%2Ftutorials\/packaging-pyqt5-apps-fbs\/\"> Twitter <\/a><\/li>\n<li><i class=\"fab fa-facebook\"><\/i> <a class=\"facebook fbc-has-badge fbc-UID_1\" href=\"https:\/\/www.facebook.com\/sharer\/sharer.php?u=https:\/\/www.pythonguis.com%2Ftutorials\/packaging-pyqt5-apps-fbs\/\"> Facebook <\/a><\/li>\n<li><i class=\"fab fa-reddit\"><\/i> <a class=\"reddit\" href=\"http:\/\/www.reddit.com\/submit?url=https:\/\/www.pythonguis.com%2Ftutorials\/packaging-pyqt5-apps-fbs\/\"> Reddit <\/a><\/li>\n<\/ul>\n<\/div>\n<\/div>\n<div id=\"container-bottom\" class=\"pure-g\">\n<div class=\"email-container pure-u-1-1\">\n<div class=\"email-signup\">\n<h4><\/h4>\n<\/div>\n<\/div>\n<\/div>\n<\/blockquote>\n","protected":false},"excerpt":{"rendered":"<p class=\"excerpt\">Packaging PyQt5 apps with fbs \u2014 Distribute cross-platform GUI applications with the fman Build System<\/p>\n<p class=\"more-link-p\"><a class=\"more-link\" href=\"https:\/\/monodes.com\/predaelli\/2021\/12\/30\/packaging-pyqt5-apps-with-fbs-distribute-cross-platform-gui-applications-with-the-fman-build-system\/\">Read more &rarr;<\/a><\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"link","meta":{"inline_featured_image":false,"jetpack_post_was_ever_published":false,"_jetpack_newsletter_access":"","_jetpack_dont_email_post_to_subs":false,"_jetpack_newsletter_tier_id":0,"_jetpack_memberships_contains_paywalled_content":false,"_jetpack_memberships_contains_paid_content":false,"activitypub_content_warning":"","activitypub_content_visibility":"","activitypub_max_image_attachments":4,"activitypub_interaction_policy_quote":"anyone","activitypub_status":"","footnotes":"","jetpack_publicize_message":"","jetpack_publicize_feature_enabled":true,"jetpack_social_post_already_shared":true,"jetpack_social_options":{"image_generator_settings":{"template":"highway","default_image_id":0,"font":"","enabled":false},"version":2}},"categories":[72,113],"tags":[],"class_list":["post-9031","post","type-post","status-publish","format-link","hentry","category-documentations","category-python","post_format-post-format-link"],"jetpack_publicize_connections":[],"jetpack_featured_media_url":"","jetpack_sharing_enabled":true,"jetpack_shortlink":"https:\/\/wp.me\/p6daft-2lF","jetpack-related-posts":[{"id":5106,"url":"https:\/\/monodes.com\/predaelli\/2019\/01\/07\/how-to-easily-build-desktop-apps-with-html-css-and-javascript\/","url_meta":{"origin":9031,"position":0},"title":"How to Easily Build Desktop Apps with HTML, CSS and Javascript","author":"Paolo Redaelli","date":"2019-01-07","format":false,"excerpt":"Can HTML, CSS and Javascript really be used to build Desktop Applications? Source: How to Easily Build Desktop Apps with HTML, CSS and Javascript How to Easily Build Desktop Apps with HTML, CSS and Javascript Aditya Sridhar Jan 4 Can HTML, CSS and Javascript really be used to build Desktop\u2026","rel":"","context":"In &quot;Documentations&quot;","block_context":{"text":"Documentations","link":"https:\/\/monodes.com\/predaelli\/category\/documentations\/"},"img":{"alt_text":"","src":"","width":0,"height":0},"classes":[]},{"id":9606,"url":"https:\/\/monodes.com\/predaelli\/2022\/09\/17\/how-to-degoogle-any-android-phone-without-root-degoogle\/","url_meta":{"origin":9031,"position":1},"title":"How to DeGoogle any Android phone WITHOUT root! : degoogle","author":"Paolo Redaelli","date":"2022-09-17","format":false,"excerpt":"Just a link. I know it is a wide and hard issue, but this can be a start: How to DeGoogle any Android phone WITHOUT root! I will show you how to uninstall\/disable any system app without root, the process takes around 2 minutes and is reversible (therefore 100% safe)\u2026","rel":"","context":"In &quot;Android&quot;","block_context":{"text":"Android","link":"https:\/\/monodes.com\/predaelli\/category\/smartphones\/android\/"},"img":{"alt_text":"","src":"","width":0,"height":0},"classes":[]},{"id":4340,"url":"https:\/\/monodes.com\/predaelli\/2018\/05\/25\/just-dont-use-proprietary-apps\/","url_meta":{"origin":9031,"position":2},"title":"&#8220;Just&#8221; don&#8217;t use proprietary apps","author":"Paolo Redaelli","date":"2018-05-25","format":false,"excerpt":"Facebook Accused of Conducting Mass Surveillance Through Its Apps - Slashdot Oh, surprise surprise! The first thing I do when I get a new phone is de-install or deactivate Facebook applications. I'm active on FB. I \"just\" keep them confined in the browser where it should stay. Ok, technically I\u2026","rel":"","context":"In &quot;Proprietary software&quot;","block_context":{"text":"Proprietary software","link":"https:\/\/monodes.com\/predaelli\/category\/software\/proprietary-software\/"},"img":{"alt_text":"","src":"https:\/\/i0.wp.com\/monodes.com\/predaelli\/wp-content\/uploads\/sites\/4\/2018\/04\/facebook_75888.png?resize=350%2C200&ssl=1","width":350,"height":200},"classes":[]},{"id":14576,"url":"https:\/\/monodes.com\/predaelli\/2025\/12\/29\/rinf-rust-in-flutter\/","url_meta":{"origin":9031,"position":3},"title":"Rinf: Rust in Flutter","author":"Paolo Redaelli","date":"2025-12-29","format":false,"excerpt":"Rust for native business logic, Flutter for flexible and beautiful GUI, Rinf is a framework for creating beautiful and performant cross-platform Rust apps by using Flutter as the UI layer. Simply add this framework to your app project, and you're all set to write Rust within Flutter! Get it at\u2026","rel":"","context":"In &quot;Flutter&quot;","block_context":{"text":"Flutter","link":"https:\/\/monodes.com\/predaelli\/category\/flutter\/"},"img":{"alt_text":"","src":"https:\/\/i0.wp.com\/monodes.com\/predaelli\/wp-content\/uploads\/sites\/4\/2025\/12\/rinf-flutter-rust.webp?fit=350%2C345&ssl=1&resize=350%2C200","width":350,"height":200},"classes":[]},{"id":14492,"url":"https:\/\/monodes.com\/predaelli\/2025\/12\/21\/tauri-2-0-tauri\/","url_meta":{"origin":9031,"position":4},"title":"Tauri 2.0 | Tauri","author":"Paolo Redaelli","date":"2025-12-21","format":false,"excerpt":"The cross-platform app building toolkit Source: Tauri 2.0 | Tauri","rel":"","context":"In &quot;Senza categoria&quot;","block_context":{"text":"Senza categoria","link":"https:\/\/monodes.com\/predaelli\/category\/senza-categoria\/"},"img":{"alt_text":"","src":"","width":0,"height":0},"classes":[]},{"id":10989,"url":"https:\/\/monodes.com\/predaelli\/2023\/12\/05\/terminals-renaissance\/","url_meta":{"origin":9031,"position":5},"title":"Terminals renaissance","author":"Paolo Redaelli","date":"2023-12-05","format":false,"excerpt":"How far have we gone since DEC VT100! All those terminal emulator have evolved a lot from the humble Xterm... In recent years we have seen several \"modern\" terminal emulators. A first wave focused on being shiny and polished or just stylish such as cool-retro-term (which is shamefully not listed\u2026","rel":"","context":"In &quot;Software&quot;","block_context":{"text":"Software","link":"https:\/\/monodes.com\/predaelli\/category\/software\/"},"img":{"alt_text":"","src":"https:\/\/i0.wp.com\/monodes.com\/predaelli\/wp-content\/uploads\/sites\/4\/2023\/12\/wave-modern-terminal.webp?fit=1200%2C799&ssl=1&resize=350%2C200","width":350,"height":200,"srcset":"https:\/\/i0.wp.com\/monodes.com\/predaelli\/wp-content\/uploads\/sites\/4\/2023\/12\/wave-modern-terminal.webp?fit=1200%2C799&ssl=1&resize=350%2C200 1x, https:\/\/i0.wp.com\/monodes.com\/predaelli\/wp-content\/uploads\/sites\/4\/2023\/12\/wave-modern-terminal.webp?fit=1200%2C799&ssl=1&resize=525%2C300 1.5x, https:\/\/i0.wp.com\/monodes.com\/predaelli\/wp-content\/uploads\/sites\/4\/2023\/12\/wave-modern-terminal.webp?fit=1200%2C799&ssl=1&resize=700%2C400 2x, https:\/\/i0.wp.com\/monodes.com\/predaelli\/wp-content\/uploads\/sites\/4\/2023\/12\/wave-modern-terminal.webp?fit=1200%2C799&ssl=1&resize=1050%2C600 3x"},"classes":[]}],"jetpack_likes_enabled":true,"_links":{"self":[{"href":"https:\/\/monodes.com\/predaelli\/wp-json\/wp\/v2\/posts\/9031","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/monodes.com\/predaelli\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/monodes.com\/predaelli\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/monodes.com\/predaelli\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/monodes.com\/predaelli\/wp-json\/wp\/v2\/comments?post=9031"}],"version-history":[{"count":0,"href":"https:\/\/monodes.com\/predaelli\/wp-json\/wp\/v2\/posts\/9031\/revisions"}],"wp:attachment":[{"href":"https:\/\/monodes.com\/predaelli\/wp-json\/wp\/v2\/media?parent=9031"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/monodes.com\/predaelli\/wp-json\/wp\/v2\/categories?post=9031"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/monodes.com\/predaelli\/wp-json\/wp\/v2\/tags?post=9031"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}