<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom"><title>dimitris.cc</title><link href="https://dimitris.cc/" rel="alternate"></link><link href="https://dimitris.cc/feed/all.xml" rel="self"></link><id>https://dimitris.cc/</id><updated>2025-06-01T11:30:00+03:00</updated><subtitle>Notes on technology and freedom</subtitle><entry><title>An interactive guitar fretboard for the Linux terminal</title><link href="https://dimitris.cc/posts/an-interactive-guitar-fretboard-for-the-linux-terminal.html" rel="alternate"></link><published>2025-06-01T11:30:00+03:00</published><updated>2025-06-01T11:30:00+03:00</updated><author><name>Dimitris</name></author><id>tag:dimitris.cc,2025-06-01:/posts/an-interactive-guitar-fretboard-for-the-linux-terminal.html</id><summary type="html">&lt;p&gt;Interested in Linux TUI applications? Learning how to play  guitar? What about the CAGED system?&lt;/p&gt;
&lt;p&gt;I suppose that you have already skipped this article. &lt;/p&gt;
&lt;p&gt;Otherwise, let me introduce you to Geekar: a guitar learning application for the Linux terminal.&lt;/p&gt;
&lt;h2&gt;Features&lt;/h2&gt;
&lt;p&gt;Let's install it.&lt;/p&gt;
&lt;p&gt;First, install &lt;code&gt;fluidsynth&lt;/code&gt; using the package manager …&lt;/p&gt;</summary><content type="html">&lt;p&gt;Interested in Linux TUI applications? Learning how to play  guitar? What about the CAGED system?&lt;/p&gt;
&lt;p&gt;I suppose that you have already skipped this article. &lt;/p&gt;
&lt;p&gt;Otherwise, let me introduce you to Geekar: a guitar learning application for the Linux terminal.&lt;/p&gt;
&lt;h2&gt;Features&lt;/h2&gt;
&lt;p&gt;Let's install it.&lt;/p&gt;
&lt;p&gt;First, install &lt;code&gt;fluidsynth&lt;/code&gt; using the package manager of your Linux distribution.&lt;/p&gt;
&lt;p&gt;Then, run:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;pipx install geekar
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;and &lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;geekar
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;The terminal shows this - or I hope so:&lt;/p&gt;
&lt;p&gt;&lt;img alt="Geekar initial page" class="blog-post-photo-centered" src="https://dimitris.cc/images/geekar-start.png"&gt;
&lt;em class="blog-post-photo-centered"&gt;Geekar initial page&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;It is an &lt;em&gt;empty&lt;/em&gt; guitar fretboard.&lt;/p&gt;
&lt;p&gt;Let's add some stuff on it. E.g., select the C major scale:&lt;/p&gt;
&lt;p&gt;&lt;img alt="Geekar C Major" class="blog-post-photo-centered" src="https://dimitris.cc/images/geekar-c-major.png"&gt;
&lt;em class="blog-post-photo-centered"&gt;C Major on Geekar&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Show the note names of the selected scale:&lt;/p&gt;
&lt;p&gt;&lt;img alt="Geekar scale notes" class="blog-post-photo-centered" src="https://dimitris.cc/images/geekar-c-major-scale-notes.png"&gt;
&lt;em class="blog-post-photo-centered"&gt;C Major notes&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Or the triad of the fifth degree (G-B-D):&lt;/p&gt;
&lt;p&gt;&lt;img alt="Geekar fifth degree triads" class="blog-post-photo-centered" src="https://dimitris.cc/images/geekar-c-major-fifth-degree.png"&gt;
&lt;em class="blog-post-photo-centered"&gt;Triad of the 5th degree&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Or just the degree numbers of the scale:&lt;/p&gt;
&lt;p&gt;&lt;img alt="Geekar degrees" class="blog-post-photo-centered" src="https://dimitris.cc/images/geekar-c-major-degrees.png"&gt;
&lt;em class="blog-post-photo-centered"&gt;Degree numbers&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Let's add some colors. Fold the &lt;em&gt;Settings&lt;/em&gt; section - just click on &lt;em&gt;Settings&lt;/em&gt; and unfold the &lt;em&gt;CAGED&lt;/em&gt; section. &lt;/p&gt;
&lt;p&gt;Select some -or all- of the patterns of the CAGED system:&lt;/p&gt;
&lt;p&gt;&lt;img alt="Geekar CAGED" class="blog-post-photo-centered" src="https://dimitris.cc/images/geekar-c-major-caged.png"&gt;
&lt;em class="blog-post-photo-centered"&gt;CAGED system&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Do the same for any key. Or change the scale type. These are the scales supported:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Major&lt;/li&gt;
&lt;li&gt;Natural minor&lt;/li&gt;
&lt;li&gt;Major pentatonic&lt;/li&gt;
&lt;li&gt;Minor pentatonic&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Unfold the &lt;em&gt;Play&lt;/em&gt; section and play the scale. Geekar suggests the finger that should be used.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Geekar Play" class="blog-post-photo-centered" src="https://dimitris.cc/images/geekar-c-major-play.png"&gt;
&lt;em class="blog-post-photo-centered"&gt;Playing the C major&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;The fretboard is interactive. Click on it and listen to the corresponding pitch.&lt;/p&gt;
&lt;h2&gt;Technology and others&lt;/h2&gt;
&lt;p&gt;The application has been written with the &lt;a href="https://textual.textualize.io/"&gt;textualize&lt;/a&gt; framework. Guitar is not the 
only musical instrument created with textualize. There is also a &lt;a href="https://github.com/eliasdorneles/upiano"&gt;piano&lt;/a&gt;; I have borrowed
ideas and even some code from it.&lt;/p&gt;
&lt;p&gt;Geekar has been created by human beings for human beings. It is a &lt;a href="https://codeberg.org/dimkard/geekar"&gt;free and open source project&lt;/a&gt;. It contains bugs. Be kind
and &lt;a href="https://codeberg.org/dimkard/geekar/issues"&gt;report&lt;/a&gt; them.&lt;/p&gt;
&lt;p&gt;Happy practising.&lt;/p&gt;</content><category term="general"></category></entry><entry><title>An ordinary day with a Linux mobile device</title><link href="https://dimitris.cc/posts/an-ordinary-day-with-a-linux-mobile-device.html" rel="alternate"></link><published>2024-07-28T15:00:00+03:00</published><updated>2024-07-28T15:00:00+03:00</updated><author><name>Dimitris</name></author><id>tag:dimitris.cc,2024-07-28:/posts/an-ordinary-day-with-a-linux-mobile-device.html</id><summary type="html">&lt;p&gt;Just an ordinary day with a mobile device running Linux. Not with a communication device, aka a phone. I believe that a phone should offer &lt;em&gt;very reliably&lt;/em&gt; just these features:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Calls&lt;/li&gt;
&lt;li&gt;SMS&lt;/li&gt;
&lt;li&gt;E2E encrypted foss IM&lt;/li&gt;
&lt;li&gt;Deep sleep and push notifications (or any alternative for notifications without draining the battery …&lt;/li&gt;&lt;/ul&gt;</summary><content type="html">&lt;p&gt;Just an ordinary day with a mobile device running Linux. Not with a communication device, aka a phone. I believe that a phone should offer &lt;em&gt;very reliably&lt;/em&gt; just these features:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Calls&lt;/li&gt;
&lt;li&gt;SMS&lt;/li&gt;
&lt;li&gt;E2E encrypted foss IM&lt;/li&gt;
&lt;li&gt;Deep sleep and push notifications (or any alternative for notifications without draining the battery)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;As far as I know, there is no such a device, is there?&lt;/p&gt;
&lt;p&gt;So, this post is not about using a Linux phone as a daily driver. It is about how I use a Linux mobile device for tasks that do not involve communications.&lt;/p&gt;
&lt;h1&gt;Device and operating system&lt;/h1&gt;
&lt;p&gt;Devices based on the Qualcomm MSM8953 Snapdragon chipset have been ported to closed-to-mainline Linux kernel. Thank you &lt;a href="https://postmarketos.org/"&gt;postmarketOS&lt;/a&gt; community. Most of the features of my old &lt;a href="https://wiki.postmarketos.org/wiki/Xiaomi_Redmi_Note_4_(xiaomi-mido)"&gt;smartphone&lt;/a&gt; are available in postmarketOS. I opted for the edge channel to get new features early. In case of any issue, I report it to the maintenance team. For graphical environment, I have chosen &lt;a href="https://sxmo.org/"&gt;SXMO&lt;/a&gt; (Sway). Everything I need is accomplished by a hackable shell script.&lt;/p&gt;
&lt;h1&gt;Tasks&lt;/h1&gt;
&lt;h2&gt;Web radio&lt;/h2&gt;
&lt;p&gt;First thing every day - OK, after coffee- is to switch on the radio. Just add some streaming addresses to a simple &lt;a href="https://codeberg.org/dimkard/sxmo-scripts/src/branch/main/simple_radio.sh"&gt;script&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;News&lt;/h2&gt;
&lt;p&gt;Every now and then I try to keep up with what's happening in the world. SXMO offers a nice sfeed-based script to fetch RSS/Atom feeds. But I prefer to get the news in a single long page of headlines followed by a short description. Something like this:&lt;/p&gt;
&lt;p&gt;&lt;img alt="sfeed_news script screenshot" class="blog-post-photo-centered-medium" src="https://dimitris.cc/images/sfeed_news_screenshot.png"&gt;
&lt;em class="blog-post-photo-centered-medium"&gt;Screenshot of sfeed_news on SXMO&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;It has been achieved by modifying the original &lt;a href="https://git.sr.ht/~mil/sxmo-utils/tree/master/item/scripts/appscripts/sxmo_rss.sh"&gt;sxmo_rss&lt;/a&gt; script.&lt;/p&gt;
&lt;p&gt;Feed subscriptions should be added to &lt;code&gt;~/.config/sxmo/news_sfeedrc&lt;/code&gt;. In the same file, the feed content location is also set:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;sfeedpath&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="ss"&gt;&amp;quot;$HOME/.local/share/sfeed_news&amp;quot;&lt;/span&gt;

&lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;list&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;of&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;feeds&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;to&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;fetch&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;
&lt;span class="n"&gt;feeds&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;feed&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;feedurl&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="n"&gt;basesiteurl&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="n"&gt;encoding&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;feed&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;Example 1&amp;#39;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;https://www.example1.com/xml/rss/all.xml&amp;#39;&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;feed&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;Example 2&amp;#39;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;https://example2.com/rss&amp;#39;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="err"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;In general, for long reads I follow an &lt;a href="https://dimitris.cc/posts/generate-epub-documents-from-rss-feeds-and-urls-and-take-your-e-book-reader-offline.html"&gt;ebook-reader-oriented workflow&lt;/a&gt;. However, to get the full, readable version of an article, &lt;em&gt;rdrview&lt;/em&gt; has been installed and a &lt;em&gt;w3m&lt;/em&gt; keymap entry has been added to &lt;code&gt;~/.w3m/keymap&lt;/code&gt;&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;keymap R COMMAND &amp;quot;READ_SHELL &amp;#39;rdrview $W3M_URL -H 2&amp;gt; /dev/null 1&amp;gt; /tmp/readable.html&amp;#39; ; LOAD /tmp/readable.html&amp;quot;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Go to a &lt;em&gt;More&lt;/em&gt; link, press &lt;em&gt;Enter&lt;/em&gt;, then &lt;em&gt;R&lt;/em&gt; and you get a readable version of it (or, I hope so).&lt;/p&gt;
&lt;h2&gt;Podcasts&lt;/h2&gt;
&lt;p&gt;I listen to podcasts. Sometimes. With a modified version of the &lt;em&gt;sxmo_rss.sh&lt;/em&gt; script.&lt;/p&gt;
&lt;p&gt;It offers:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Subscriptions as menu entries&lt;/li&gt;
&lt;li&gt;For each subscription, a list of entries for each episode&lt;/li&gt;
&lt;li&gt;For each episode, three actions: &lt;ol&gt;
&lt;li&gt;Get more info about the episode&lt;/li&gt;
&lt;li&gt;Listen&lt;/li&gt;
&lt;li&gt;Download&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;em&gt;Fetch&lt;/em&gt; to get the latest list of subscriptions and episodes:&lt;/p&gt;
&lt;p&gt;&lt;img alt="sfeed_podcast script screenshot_1" class="blog-post-photo-centered-medium" src="https://dimitris.cc/images/sfeed_podcast_1.png"&gt;
&lt;em class="blog-post-photo-centered-medium"&gt;sfeed_podcast initial menu&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Find the content you like: &lt;/p&gt;
&lt;p&gt;&lt;img alt="sfeed_podcast script screenshot_2" class="blog-post-photo-centered-medium" src="https://dimitris.cc/images/sfeed_podcast_2.png"&gt;
&lt;em class="blog-post-photo-centered-medium"&gt;sfeed_podcast subscriptions menu&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;img alt="sfeed_podcast script screenshot_3" class="blog-post-photo-centered-medium" src="https://dimitris.cc/images/sfeed_podcast_3.png"&gt;
&lt;em class="blog-post-photo-centered-medium"&gt;sfeed_podcast episodes menu&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Play or download it:&lt;/p&gt;
&lt;p&gt;&lt;img alt="sfeed_podcast script screenshot_4" class="blog-post-photo-centered-medium" src="https://dimitris.cc/images/sfeed_podcast_4.png"&gt;
&lt;em class="blog-post-photo-centered-medium"&gt;sfeed_podcast actions menu&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Subscriptions and feed content location should be set in &lt;code&gt;~/.config/sxmo/podcasts_sfeedrc&lt;/code&gt;, e.g.:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;sfeedpath&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="ss"&gt;&amp;quot;$HOME/.local/share/sfeed_podcasts&amp;quot;&lt;/span&gt;

&lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;list&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;of&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;feeds&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;to&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;fetch&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;
&lt;span class="n"&gt;feeds&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;feed&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;feedurl&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="n"&gt;basesiteurl&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="n"&gt;encoding&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;feed&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;&amp;quot;Cory Doctorow&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;&amp;quot;https://craphound.com/feed/&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;feed&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;&amp;quot;postmarketOS&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;&amp;quot;https://cast.postmarketos.org/feed.rss&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;feed&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;&amp;quot;FSFE&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;&amp;quot;https://fsfe.org/news/podcast.en.rss&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;feed&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;&amp;quot;How to fix the internet&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;&amp;quot;https://feeds.eff.org/howtofixtheinternet&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;
&lt;span class="err"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Podcasts are saved in &lt;code&gt;~/Podcasts&lt;/code&gt;. Use the &lt;em&gt;Files&lt;/em&gt; script to play downloaded files.&lt;/p&gt;
&lt;h2&gt;Musical practice&lt;/h2&gt;
&lt;p&gt;For musical practice I need a metronome and backing tracks.&lt;/p&gt;
&lt;p&gt;Some time ago I wrote a simple metronome &lt;a href="https://codeberg.org/dimkard/simple-metronome"&gt;script&lt;/a&gt; based on &lt;em&gt;sox&lt;/em&gt; and &lt;em&gt;mpv&lt;/em&gt;. It works OK.&lt;/p&gt;
&lt;p&gt;&lt;img alt="simple_metronome_1" class="blog-post-photo-centered-medium" src="https://dimitris.cc/images/simple_metronome_1.png"&gt;
&lt;em class="blog-post-photo-centered-medium"&gt;simple_metronome beat selection menu&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Already downloaded tracks can be directly played using the &lt;em&gt;Files&lt;/em&gt; script. To get new ones, this &lt;a href="https://git.sr.ht/~anjan/sxmo-userscripts/tree/master/item/scripts/sxmo_invidious.sh"&gt;script&lt;/a&gt; has been &lt;a href="https://codeberg.org/dimkard/sxmo-scripts/src/branch/main/sxmo_invidious_w3m.sh"&gt;modified&lt;/a&gt;, to present video search results in a single html page:&lt;/p&gt;
&lt;p&gt;&lt;img alt="sxmo_invidious_w3m_1" class="blog-post-photo-centered-medium" src="https://dimitris.cc/images/sxmo_invidious_w3m_1.png"&gt;
&lt;em class="blog-post-photo-centered-medium"&gt;sxmo_invidious_w3m search results&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;To facilitate direct playing, &lt;em&gt;mpv&lt;/em&gt; has been added as external browser in &lt;code&gt;~/.w3m/config&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;extbrowser xdg-openP
extbrowser2 /usr/bin/mpv
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;alongside with a couple of entries in ~/.w3m/keymap:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;keymap m EXTERN_LINK
keymap M EXTERN
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;If the cursor is on a &lt;em&gt;More&lt;/em&gt; link, video can be directly played by pressing "2, m".&lt;/p&gt;
&lt;h1&gt;Caveats&lt;/h1&gt;
&lt;p&gt;The volume of the device is very low. On the bright side, bluetooth works perfectly. Well, for bluetooth. &lt;/p&gt;
&lt;p&gt;Headphones -the ones with a cable- are &lt;a href="https://gitlab.com/postmarketOS/pmaports/-/issues/1582"&gt;haunted&lt;/a&gt; by spooky noise when audio play ends. There is a &lt;a href="https://gitlab.com/postmarketOS/pmaports/-/issues/1582#note_1883841573"&gt;workaround&lt;/a&gt; but a pair of bluetooth headphones is a wiser choice.&lt;/p&gt;
&lt;h1&gt;Conclusion&lt;/h1&gt;
&lt;p&gt;This setup works &lt;em&gt;very reliably&lt;/em&gt;. It does what is expected to do and it does not crash. Great.&lt;/p&gt;</content><category term="general"></category></entry><entry><title>Generate EPUB documents from RSS feeds and URLs and take your e-book reader offline</title><link href="https://dimitris.cc/posts/generate-epub-documents-from-rss-feeds-and-urls-and-take-your-e-book-reader-offline.html" rel="alternate"></link><published>2023-09-10T12:50:00+03:00</published><updated>2023-09-10T12:50:00+03:00</updated><author><name>Dimitris</name></author><id>tag:dimitris.cc,2023-09-10:/posts/generate-epub-documents-from-rss-feeds-and-urls-and-take-your-e-book-reader-offline.html</id><summary type="html">&lt;p&gt;I like e-book readers. In a couple of minutes, you can get the books you like and get started with reading them. Or RSS feeds and that random article you have marked as "read-it-later". The reading experience is nice. Thanks to the e-ink technology, eye strain is drastically reduced. You …&lt;/p&gt;</summary><content type="html">&lt;p&gt;I like e-book readers. In a couple of minutes, you can get the books you like and get started with reading them. Or RSS feeds and that random article you have marked as "read-it-later". The reading experience is nice. Thanks to the e-ink technology, eye strain is drastically reduced. You can also set font type, size, line spacing and margins as you wish. Great.&lt;/p&gt;
&lt;p&gt;I don't like e-book readers. I trust devices that run maintained, free software. Most e-book devices run on custom, unmaintained Linux kernels. Others, depend on proprietary software. I don't trust e-book reader devices. And their cost is not insignificant. Not great.&lt;/p&gt;
&lt;p&gt;So, I took my humble e-book reader offline. It communicates with the external world with an SD card.&lt;/p&gt;
&lt;p&gt;And I created a simple terminal utility which I run on a trusted device. It does this:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Mounts an SD card&lt;/li&gt;
&lt;li&gt;Fetches RSS feeds&lt;/li&gt;
&lt;li&gt;Downloads read-it-later articles&lt;/li&gt;
&lt;li&gt;Converts them to e-pub documents&lt;/li&gt;
&lt;li&gt;Saves them to the SD card&lt;/li&gt;
&lt;li&gt;Unmounts the SD card&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Instead of an SD card, it can also save the e-pub documents to a local &lt;em&gt;~/path/to&lt;/em&gt; directory.&lt;/p&gt;
&lt;p&gt;The utility can be found on &lt;a href="https://codeberg.org/dimkard/simple-newspaper"&gt;Codeberg&lt;/a&gt;. It is written in Python, it makes use of &lt;a href="https://calibre-ebook.com/"&gt;Calibre&lt;/a&gt; for the RSS part and &lt;a href="https://github.com/buriy/python-readability"&gt;python-readability&lt;/a&gt; for the articles.&lt;/p&gt;
&lt;p&gt;RSS feeds should be created as Calibre &lt;a href="https://manual.calibre-ebook.com/news.html"&gt;recipes&lt;/a&gt;. The configuration of the recipes can be found at &lt;em&gt;~/.config/calibre/custom_recipes&lt;/em&gt; or, if Calibre has been installed as flatpak, at &lt;em&gt;~/.var/app/com.calibre_ebook.calibre/config/calibre/custom_recipes&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;The read-it-later links should be added to a plain text file, e.g. &lt;em&gt;~/Documents/read-it-later.txt&lt;/em&gt;, like this:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;www&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;example&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;com&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;my_article&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;html&lt;/span&gt;
&lt;span class="n"&gt;Another&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;article&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;www&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;example&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;com&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;another_article&lt;/span&gt;
&lt;span class="n"&gt;Yet&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;another&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="n"&gt;article&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;www&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;example&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;com&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;yet_another&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;html&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;You are going to need the &lt;em&gt;uuid&lt;/em&gt; of your SD card. Run &lt;code&gt;ls -l /dev/disk/by-uuid/&lt;/code&gt;. Then, plug the SD card into your computer. Run &lt;code&gt;ls -l /dev/disk/by-uuid/&lt;/code&gt; again. The new line contains the &lt;em&gt;uiid&lt;/em&gt; of the SD card.&lt;/p&gt;
&lt;p&gt;A couple of directories are need as well:
- the directory that the SD card should be mounted to, e.g. &lt;em&gt;/mnt/jane/sd&lt;/em&gt;.
- the target directory for the e-pub documents. It should be a relative path on the SD card, e.g., &lt;em&gt;newspaper/news&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;To install the Python dependencies, run &lt;code&gt;pip install -r requirements.txt&lt;/code&gt;. A &lt;a href="https://packaging.python.org/en/latest/guides/installing-using-pip-and-virtual-environments/"&gt;virtual environment&lt;/a&gt; is a good idea.&lt;/p&gt;
&lt;p&gt;Finally, run:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;python&lt;span class="w"&gt; &lt;/span&gt;ebook_newspaper.py&lt;span class="w"&gt; &lt;/span&gt;--recipes_dir&lt;span class="w"&gt; &lt;/span&gt;~/.config/calibre/custom_recipes&lt;span class="w"&gt; &lt;/span&gt;--target_device&lt;span class="w"&gt; &lt;/span&gt;/dev/disk/by-uuid/7afb-3231&lt;span class="w"&gt; &lt;/span&gt;--target_mount_point&lt;span class="w"&gt; &lt;/span&gt;/mnt/jane/sd&lt;span class="w"&gt; &lt;/span&gt;--target_dir&lt;span class="w"&gt; &lt;/span&gt;newspaper/news&lt;span class="w"&gt; &lt;/span&gt;--read_it_later_urls&lt;span class="w"&gt; &lt;/span&gt;~/Documents/read-it-later.txt
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;or, if you don't need an SD card:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;python&lt;span class="w"&gt; &lt;/span&gt;ebook_newspaper.py&lt;span class="w"&gt; &lt;/span&gt;--recipes_dir&lt;span class="w"&gt; &lt;/span&gt;~/.config/calibre/custom_recipes&lt;span class="w"&gt; &lt;/span&gt;--home_target_dir&lt;span class="w"&gt; &lt;/span&gt;~/Documents/newspaper&lt;span class="w"&gt; &lt;/span&gt;--read_it_later_urls&lt;span class="w"&gt; &lt;/span&gt;~/Documents/read-it-later.txt
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;If Calibre has been installed as flatpak, add the &lt;code&gt;--flatpak&lt;/code&gt; option:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;python&lt;span class="w"&gt; &lt;/span&gt;ebook_newspaper.py&lt;span class="w"&gt; &lt;/span&gt;--flatpak&lt;span class="w"&gt; &lt;/span&gt;--recipes_dir&lt;span class="w"&gt; &lt;/span&gt;~/.var/app/com.calibre_ebook.calibre/config/calibre/custom_recipes&lt;span class="w"&gt; &lt;/span&gt;--target_device&lt;span class="w"&gt; &lt;/span&gt;/dev/disk/by-uuid/7afb-3231&lt;span class="w"&gt; &lt;/span&gt;--target_mount_point&lt;span class="w"&gt; &lt;/span&gt;/mnt/jane/sd&lt;span class="w"&gt; &lt;/span&gt;--target_dir&lt;span class="w"&gt; &lt;/span&gt;newspaper/news&lt;span class="w"&gt; &lt;/span&gt;--read_it_later_urls&lt;span class="w"&gt; &lt;/span&gt;~/Documents/read-it-later.txt
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;To archive the older e-pub documents on each script execution, set the &lt;code&gt;--archive&lt;/code&gt; option:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;python&lt;span class="w"&gt; &lt;/span&gt;ebook_newspaper.py&lt;span class="w"&gt; &lt;/span&gt;--archive&lt;span class="w"&gt; &lt;/span&gt;--recipes_dir&lt;span class="w"&gt; &lt;/span&gt;~/.var/app/com.calibre_ebook.calibre/config/calibre/custom_recipes&lt;span class="w"&gt; &lt;/span&gt;--target_device&lt;span class="w"&gt; &lt;/span&gt;/dev/disk/by-uuid/7afb-3231&lt;span class="w"&gt; &lt;/span&gt;--target_mount_point&lt;span class="w"&gt; &lt;/span&gt;/mnt/jane/sd&lt;span class="w"&gt; &lt;/span&gt;--target_dir&lt;span class="w"&gt; &lt;/span&gt;newspaper/news&lt;span class="w"&gt; &lt;/span&gt;--read_it_later_urls&lt;span class="w"&gt; &lt;/span&gt;~/Documents/read-it-later.txt
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Happy reading.&lt;/p&gt;</content><category term="general"></category></entry><entry><title>Glossaico 1.2 is out</title><link href="https://dimitris.cc/posts/glossaico-12-is-out.html" rel="alternate"></link><published>2023-03-18T14:30:00+02:00</published><updated>2023-03-18T14:30:00+02:00</updated><author><name>Dimitris</name></author><id>tag:dimitris.cc,2023-03-18:/posts/glossaico-12-is-out.html</id><summary type="html">&lt;p&gt;Glossaico 1.2 has been released. Glossaico is a free software application for language learning based on &lt;a href="https://librelingo.app"&gt;LibreLingo&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;New features&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Desktop users can navigate using the keyboard&lt;/li&gt;
&lt;li&gt;Practice can be canceled without saving progress&lt;/li&gt;
&lt;li&gt;Translation infrastructure has been set up, and the user interface has been translated to Dutch, German …&lt;/li&gt;&lt;/ul&gt;</summary><content type="html">&lt;p&gt;Glossaico 1.2 has been released. Glossaico is a free software application for language learning based on &lt;a href="https://librelingo.app"&gt;LibreLingo&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;New features&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Desktop users can navigate using the keyboard&lt;/li&gt;
&lt;li&gt;Practice can be canceled without saving progress&lt;/li&gt;
&lt;li&gt;Translation infrastructure has been set up, and the user interface has been translated to Dutch, German, Greek, Italian, Polish, and Ukrainian. Many thanks to mondstern, ewm, Lamdarer, Hiajen, and albano.battistella for their work on translation.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img alt="Glossaico in Greek" class="blog-post-photo-centered-medium" src="https://dimitris.cc/images/greek_glossaico.png"&gt;
&lt;em class="blog-post-photo-centered"&gt;Glossaico translated to Greek&lt;/em&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Audio plays automatically on listening challenges&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Fixes&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;An issue that prevented progress from being saved has been resolved&lt;/li&gt;
&lt;li&gt;KDE themes can be properly applied&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img alt="Breeze Dark theme applied" class="blog-post-photo-centered-medium" src="https://dimitris.cc/images/glossaico_breeze_dark.png"&gt;
&lt;em class="blog-post-photo-centered"&gt;Glossaico running on KDE Plasma with the Breeze Dark theme&lt;/em&gt;&lt;/p&gt;
&lt;h2&gt;Install&lt;/h2&gt;
&lt;p&gt;A flatpak build of Glossaico is available on &lt;a href="https://flathub.org/apps/details/org.codeberg.dimkard.glossaico"&gt;flathub&lt;/a&gt;. If the software center of your distribution supports flatpak and the flathub repository has been configured, use it to install Glossaico. You can also &lt;a href="https://codeberg.org/dimkard/glossaico#contribute"&gt;build&lt;/a&gt; it from the source code.&lt;/p&gt;
&lt;h2&gt;Source code&lt;/h2&gt;
&lt;p&gt;The release source archives can be found &lt;a href="https://codeberg.org/dimkard/glossaico/releases/tag/v1.2"&gt;here&lt;/a&gt;, signed with this &lt;a href="https://keys.openpgp.org/vks/v1/by-fingerprint/D528D3E445FDED425902FA26FBCE495D1EEB565E"&gt;key&lt;/a&gt;, with fingerprint D528 D3E4 45FD ED42 5902  FA26 FBCE 495D 1EEB 565E.&lt;/p&gt;
&lt;h2&gt;Get involved&lt;/h2&gt;
&lt;p&gt;Glossaico is powered by the work of the LibreLingo community. You can get involved by joining the &lt;a href="https://matrix.to/#/#librelingo:matrix.org"&gt;matrix&lt;/a&gt; channel and get started with contributing to the &lt;a href="https://librelingo.app/docs/"&gt;platform&lt;/a&gt; or to &lt;a href="https://github.com/LibreLingo/LibreLingo/tree/main/docs/courses"&gt;courses&lt;/a&gt;. For Glossaico, please report issues and provide your ideas on the &lt;a href="https://codeberg.org/dimkard/glossaico/issues"&gt;bug tracker&lt;/a&gt; of the application, submit pull requests, or contribute to the &lt;a href="https://translate.codeberg.org/engage/glossaico/"&gt;translation&lt;/a&gt; of the application.&lt;/p&gt;</content><category term="general"></category></entry><entry><title>Glossaico 1.1 is available</title><link href="https://dimitris.cc/posts/glossaico-11-is-available.html" rel="alternate"></link><published>2022-11-14T18:30:00+02:00</published><updated>2022-11-14T18:30:00+02:00</updated><author><name>Dimitris</name></author><id>tag:dimitris.cc,2022-11-14:/posts/glossaico-11-is-available.html</id><summary type="html">&lt;p&gt;Glossaico 1.1 has been released. Glossaico is a free software application for language learning based on &lt;a href="https://librelingo.app"&gt;LibreLingo&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;What's new&lt;/h2&gt;
&lt;p&gt;Most of the work done for this release is related to code refactoring, testing and maintainability improvements. However, Glossaico users may be interested in a set of changes included in …&lt;/p&gt;</summary><content type="html">&lt;p&gt;Glossaico 1.1 has been released. Glossaico is a free software application for language learning based on &lt;a href="https://librelingo.app"&gt;LibreLingo&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;What's new&lt;/h2&gt;
&lt;p&gt;Most of the work done for this release is related to code refactoring, testing and maintainability improvements. However, Glossaico users may be interested in a set of changes included in this release.&lt;/p&gt;
&lt;h3&gt;Content update&lt;/h3&gt;
&lt;p&gt;Course content and application updates have been decoupled. In specific, as soon as new modules, skills and challenges are available for the existing language courses -or even whole new courses- users can immediately install them, without a new Glossaico release to be needed.&lt;/p&gt;
&lt;h3&gt;Less courses, more courses&lt;/h3&gt;
&lt;p&gt;Although on the previous version of Glossaico three language courses were offered (Spanish, German and Basque for English speakers), with this release just the Spanish from English course is shown by default. That's because this course is the only ready-for-users one according to the LibreLingo community. Nevertheless, Glossaico users can opt to practice courses that are not 100% ready yet, by navigating to the &lt;em&gt;Configure&lt;/em&gt; page and enabling the &lt;em&gt;Show courses in beta phase&lt;/em&gt; option. Rough edges and bugs should be expected for these courses, feel free to report any issues found.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Available courses, beta included" class="blog-post-photo-centered-medium" src="https://dimitris.cc/images/courses_model.png"&gt;
&lt;em class="blog-post-photo-centered"&gt;Available courses, beta ones included&lt;/em&gt;&lt;/p&gt;
&lt;h3&gt;Practicing and progress&lt;/h3&gt;
&lt;p&gt;The practice session flow -which challenges are offered, the way they are ordered, repetition intervals, etc- has been updated and now it is closer to the LibreLingo web application. The same applies for the calculation of the practice progress.&lt;/p&gt;
&lt;h3&gt;User interface and performance&lt;/h3&gt;
&lt;p&gt;Several user interface changes and performance improvements have also been included in Glossaico 1.1:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Language courses load faster, thanks to improvements in LibreLingo packages as well as to work on Glossaico&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Cleaner and simpler user interface&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Skill introduction pages are shown, when available:&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img alt="Spanish introduction" class="blog-post-photo-centered-medium" src="https://dimitris.cc/images/spanish_introduction.png"&gt;&lt;/p&gt;
&lt;p&gt;&lt;em class="blog-post-photo-centered"&gt;Spanish introduction page&lt;/em&gt;&lt;/p&gt;
&lt;h2&gt;Install&lt;/h2&gt;
&lt;p&gt;A flatpak build of Glossaico is available on &lt;a href="https://flathub.org/apps/details/org.codeberg.dimkard.glossaico"&gt;flathub&lt;/a&gt;. If the software center of your distribution supports flatpak and the flathub repository has been configured, just use it to install Glossaico. You can also &lt;a href="https://codeberg.org/dimkard/glossaico#contribute"&gt;build&lt;/a&gt; it from the source code.&lt;/p&gt;
&lt;h2&gt;Source code&lt;/h2&gt;
&lt;p&gt;The release source archives can be found &lt;a href="https://codeberg.org/dimkard/glossaico/releases/tag/v1.1"&gt;here&lt;/a&gt;, signed with the &lt;a href="https://keys.openpgp.org/vks/v1/by-fingerprint/D528D3E445FDED425902FA26FBCE495D1EEB565E"&gt;key&lt;/a&gt; related to my email address, with fingerprint D528 D3E4 45FD ED42 5902  FA26 FBCE 495D 1EEB 565E.&lt;/p&gt;
&lt;h2&gt;Get involved&lt;/h2&gt;
&lt;p&gt;Glossaico is powered by the work of the LibreLingo community. You can get involved by joining the &lt;a href="https://matrix.to/#/#librelingo:matrix.org"&gt;matrix&lt;/a&gt; channel and get started with contributing to the &lt;a href="https://librelingo.app/docs/"&gt;platform&lt;/a&gt; or to &lt;a href="https://github.com/LibreLingo/LibreLingo/tree/main/docs/courses"&gt;courses&lt;/a&gt;. For Glossaico, please report issues and provide your ideas on the &lt;a href="https://codeberg.org/dimkard/glossaico/issues"&gt;bug tracker&lt;/a&gt; of the application or submit pull requests.&lt;/p&gt;</content><category term="general"></category></entry><entry><title>Linux applications with Python and QML</title><link href="https://dimitris.cc/posts/linux-applications-with-python-and-qml.html" rel="alternate"></link><published>2022-02-26T13:00:00+02:00</published><updated>2022-02-26T13:00:00+02:00</updated><author><name>Dimitris</name></author><id>tag:dimitris.cc,2022-02-26:/posts/linux-applications-with-python-and-qml.html</id><summary type="html">&lt;p&gt;Linux applications with QML and Python? Why not? Python is a popular programming language. QML offers an intuitive way to create user interfaces. Kirigami provides useful UI components and implements UI/UX patterns for mobile and desktop. Let's fit these technologies together and create a simple application.&lt;/p&gt;
&lt;h1&gt;Prerequisites&lt;/h1&gt;
&lt;p&gt;Obviously, we …&lt;/p&gt;</summary><content type="html">&lt;p&gt;Linux applications with QML and Python? Why not? Python is a popular programming language. QML offers an intuitive way to create user interfaces. Kirigami provides useful UI components and implements UI/UX patterns for mobile and desktop. Let's fit these technologies together and create a simple application.&lt;/p&gt;
&lt;h1&gt;Prerequisites&lt;/h1&gt;
&lt;p&gt;Obviously, we need Python. Most Linux distributions provide a recent Python. In the rare case that you have to install it by yourself, you may find &lt;a href="https://wiki.python.org/moin/BeginnersGuide/Download"&gt;here&lt;/a&gt; some tips. For QML and Kirigami, check the Kirigami &lt;a href="https://develop.kde.org/docs/use/kirigami/introduction-getting_started/"&gt;introduction&lt;/a&gt; guide. &lt;/p&gt;
&lt;p&gt;Between Python and QML there is &lt;a href="https://doc.qt.io/qtforpython-5/"&gt;Qt for Python&lt;/a&gt;. It offers Python bindings for Qt, and it consists of:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;PySide2, so that you can use Qt5 APIs in Python&lt;/li&gt;
&lt;li&gt;Shiboken2, a binding generator tool which exposes C++ to Python&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;On Ubuntu and the like, &lt;code&gt;python3-pyside2.qtcore&lt;/code&gt;, &lt;code&gt;python3-pyside2.qtqml&lt;/code&gt; and &lt;code&gt;python3-pyside2.qtwidgets&lt;/code&gt; are enough for the code of this article. Different distributions may package or name the various bindings differently. E.g., on postmarketOS, everything we need is found in the &lt;code&gt;py3-pyside2&lt;/code&gt; package.&lt;/p&gt;
&lt;h1&gt;Development&lt;/h1&gt;
&lt;p&gt;Let's create a simple program to see these technologies in action: a markdown viewer. The UI will be created in QML and the logic in Python. Users will write some markdown text, press a button, and the formatted text will be shown.&lt;/p&gt;
&lt;p&gt;Nothing fancy. &lt;/p&gt;
&lt;p&gt;It is &lt;a href="https://doc.qt.io/qtforpython-5/quickstart.html"&gt;recommended&lt;/a&gt; to use a virtual environment. The &lt;a href="https://docs.python.org/3/library/venv.html"&gt;venv&lt;/a&gt; module provides support for virtual environments with their own site directories, optionally isolated from system site directories. On Ubuntu flavors and derivatives, it is provided by the &lt;code&gt;python3.8-venv&lt;/code&gt; package.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Create a directory and a virtual environment for the project:&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nx"&gt;mkdir&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;simple&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;md&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;viewer&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;project&lt;/span&gt;
&lt;span class="nx"&gt;cd&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;simple&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;md&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;viewer&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;project&lt;/span&gt;
&lt;span class="nx"&gt;python3&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;m&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;venv&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;--&lt;/span&gt;&lt;span class="nx"&gt;system&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;site&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;packages&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;ul&gt;
&lt;li&gt;Activate it using the &lt;em&gt;activate&lt;/em&gt; script:&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;source env/bin/activate
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;We can verify that we are working in a virtual environment by checking the &lt;code&gt;VIRTUAL_ENV&lt;/code&gt; environment variable.&lt;/p&gt;
&lt;p&gt;It's time to write some code. A PySide2/QML application consists, at least, of two files: a file with the QML description of the user interface, and a Python file that loads the QML file.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Create a new &lt;code&gt;simplemdviewer&lt;/code&gt; directory into &lt;code&gt;simple-md-viewer-project&lt;/code&gt;, and add a new &lt;code&gt;simplemdviewer_app.py&lt;/code&gt; file in this directory:&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;os&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;PySide2.QtGui&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;QGuiApplication&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;PySide2.QtCore&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;QUrl&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;PySide2.QtQml&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;QQmlApplicationEngine&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;qmlRegisterType&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&amp;quot;&amp;quot;Initializes and manages the application execution&amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;QGuiApplication&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;engine&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;QQmlApplicationEngine&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;base_path&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;abspath&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;dirname&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="vm"&gt;__file__&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;QUrl&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;file://&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;base_path&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s1"&gt;/qml/main.qml&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;engine&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;load&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;engine&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;rootObjects&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;quit&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;exec_&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="vm"&gt;__name__&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;__main__&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;em class="blog-post-photo-centered"&gt;simplemdviewer_app.py&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;We have just created a &lt;em&gt;QGuiApplication&lt;/em&gt; object that, among others, initializes the application and contains the main event loop. The &lt;em&gt;QQmlApplicationEngine&lt;/em&gt; object loads the QML &lt;code&gt;main.qml&lt;/code&gt; file.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Create a new &lt;code&gt;qml&lt;/code&gt; directory in the &lt;code&gt;simplemdviewer&lt;/code&gt; one and add a new &lt;code&gt;main.qml&lt;/code&gt; file that specifies the UI of the application:&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;QtQuick&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;2.12&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;QtQuick.Controls&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;2.12&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;as&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Controls&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;org.kde.kirigami&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;2.12&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;as&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Kirigami&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;QtQuick.Layouts&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;1.12&lt;/span&gt;

&lt;span class="n"&gt;Kirigami&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ApplicationWindow&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;root&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;qsTr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Simple markdown viewer&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;minimumWidth&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Kirigami&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Units&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;gridUnit&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;20&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;minimumHeight&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Kirigami&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Units&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;gridUnit&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;20&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;pageStack&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;initialPage&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;initPage&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;Component&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;initPage&lt;/span&gt;

&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;Kirigami&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Page&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;qsTr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Markdown Viewer&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="n"&gt;ColumnLayout&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="n"&gt;anchors&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;                    &lt;/span&gt;&lt;span class="n"&gt;top&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;parent&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;top&lt;/span&gt;
&lt;span class="w"&gt;                    &lt;/span&gt;&lt;span class="n"&gt;left&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;parent&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;left&lt;/span&gt;
&lt;span class="w"&gt;                    &lt;/span&gt;&lt;span class="n"&gt;right&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;parent&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;right&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="n"&gt;Controls&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;TextArea&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;                    &lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;sourceArea&lt;/span&gt;

&lt;span class="w"&gt;                    &lt;/span&gt;&lt;span class="n"&gt;placeholderText&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;qsTr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Write here some markdown code&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;                    &lt;/span&gt;&lt;span class="n"&gt;wrapMode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Text&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WrapAnywhere&lt;/span&gt;
&lt;span class="w"&gt;                    &lt;/span&gt;&lt;span class="n"&gt;Layout&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;fillWidth&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;true&lt;/span&gt;
&lt;span class="w"&gt;                    &lt;/span&gt;&lt;span class="n"&gt;Layout&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;minimumHeight&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Kirigami&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Units&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;gridUnit&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="n"&gt;RowLayout&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;                    &lt;/span&gt;&lt;span class="n"&gt;Layout&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;fillWidth&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;true&lt;/span&gt;

&lt;span class="w"&gt;                    &lt;/span&gt;&lt;span class="n"&gt;Controls&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Button&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;                        &lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;qsTr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Format&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="w"&gt;                        &lt;/span&gt;&lt;span class="n"&gt;onClicked&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;formattedText&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;sourceArea&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;
&lt;span class="w"&gt;                    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="w"&gt;                    &lt;/span&gt;&lt;span class="n"&gt;Controls&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Button&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;                        &lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;qsTr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Clear&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="w"&gt;                        &lt;/span&gt;&lt;span class="n"&gt;onClicked&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;                            &lt;/span&gt;&lt;span class="n"&gt;sourceArea&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;                            &lt;/span&gt;&lt;span class="n"&gt;formattedText&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;                        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;                    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;

&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="n"&gt;Text&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;                    &lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;formattedText&lt;/span&gt;

&lt;span class="w"&gt;                    &lt;/span&gt;&lt;span class="n"&gt;textFormat&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Text&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;RichText&lt;/span&gt;
&lt;span class="w"&gt;                    &lt;/span&gt;&lt;span class="n"&gt;wrapMode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Text&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WordWrap&lt;/span&gt;
&lt;span class="w"&gt;                    &lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;sourceArea&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;

&lt;span class="w"&gt;                    &lt;/span&gt;&lt;span class="n"&gt;Layout&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;fillWidth&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;true&lt;/span&gt;
&lt;span class="w"&gt;                    &lt;/span&gt;&lt;span class="n"&gt;Layout&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;minimumHeight&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Kirigami&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Units&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;gridUnit&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;em class="blog-post-photo-centered"&gt;main.qml&lt;/em&gt; &lt;/p&gt;
&lt;p&gt;We have just created a new QML-Kirigami-Python application.&lt;/p&gt;
&lt;p&gt;Cool. &lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Run it:&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nx"&gt;python3&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;simplemdviewer_app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;py&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;It shows a page split into two sections. On the top one you write some markdown code. Upon clicking on &lt;em&gt;Format&lt;/em&gt;, the formated text will be displayed on the bottom of the page.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Simple Markdown Viewer" class="blog-post-photo-centered-medium" src="https://dimitris.cc/images/simple-md-viewer-1.png"&gt;
&lt;em class="blog-post-photo-centered"&gt;A simple Python QML application&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;At the moment we have not used any Python interesting stuff. In reality, the application can also run as a standalone QML one:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;qmlscene qml/main.qml
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;It does not format anything; if we click on &lt;em&gt;Format&lt;/em&gt; it just spits the unformatted text into a text element.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Nothing special" class="blog-post-photo-centered-medium" src="https://dimitris.cc/images/simple-md-viewer-2.png"&gt;
&lt;em class="blog-post-photo-centered"&gt;Nothing special&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;OK, let's add some Python logic: a simple markdown converter in a Python, QObject derivative class.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Create a new &lt;code&gt;md_converter.py&lt;/code&gt; file in the &lt;code&gt;simplemdviewer&lt;/code&gt; directory:&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;markdown&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;markdown&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;PySide2.QtCore&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;QObject&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Signal&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Slot&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Property&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nc"&gt;MdConverter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;QObject&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&amp;quot;&amp;quot;A simple markdown converter&amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="fm"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;QObject&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="fm"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_source_text&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;&amp;#39;&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;readSourceText&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_source_text&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;setSourceText&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;val&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_source_text&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;val&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sourceTextChanged&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;emit&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nd"&gt;@Signal&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;sourceTextChanged&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;pass&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nd"&gt;@Slot&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;mdFormat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;markdown&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_source_text&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;sourceText&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Property&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;readSourceText&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;setSourceText&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;notify&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;sourceTextChanged&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;em class="blog-post-photo-centered"&gt;md_converter.py&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;The &lt;em&gt;MdConverter&lt;/em&gt; class contains the &lt;em&gt;source_text&lt;/em&gt; member variable. The &lt;em&gt;sourceText&lt;/em&gt; property exposes &lt;em&gt;source_text&lt;/em&gt; to the QML system through the &lt;em&gt;readSouceText&lt;/em&gt; getter and the &lt;em&gt;setSourceText&lt;/em&gt; setter functions. When setting the &lt;em&gt;sourceText&lt;/em&gt;, the &lt;em&gt;sourceTextChanged&lt;/em&gt; signal is emitted to let QML know that the property has changed. The &lt;em&gt;mdFormat&lt;/em&gt; function returns the markdown formatted text and it has been declared as &lt;em&gt;Slot&lt;/em&gt; so as to be invokable by the QML code.&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;markdown&lt;/code&gt; Python package takes care of formatting. Let's install it in our virtual environment.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Execute:&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;pip install markdown
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;It is time to register the new &lt;em&gt;MdConverter&lt;/em&gt; type.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Update the &lt;code&gt;simplemdviewer_app.py&lt;/code&gt; file to:&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;os&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;PySide2.QtGui&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;QGuiApplication&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;PySide2.QtCore&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;QUrl&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;PySide2.QtQml&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;QQmlApplicationEngine&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;qmlRegisterType&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;md_converter&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;MdConverter&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&amp;quot;&amp;quot;Initializes and manages the application execution&amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;QGuiApplication&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;engine&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;QQmlApplicationEngine&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;qmlRegisterType&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;MdConverter&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;org.mydomain.simplemdviewer&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;                    &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;MdConverter&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;base_path&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;abspath&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;dirname&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="vm"&gt;__file__&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;QUrl&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;file://&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;base_path&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s1"&gt;/qml/main.qml&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;engine&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;load&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;engine&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;rootObjects&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;quit&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;exec_&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="vm"&gt;__name__&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;__main__&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;em class="blog-post-photo-centered"&gt;simplemdviewer_app.py&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;The &lt;em&gt;qmlRegisterType&lt;/em&gt; function has registered the &lt;em&gt;MdCoverter&lt;/em&gt; type in the QML system, in the library &lt;em&gt;org.mydomain.simplemdviewer&lt;/em&gt;, version 1.0.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Change the &lt;code&gt;qml/main.qml&lt;/code&gt; file to:&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;QtQuick&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;2.12&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;QtQuick.Controls&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;2.12&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;as&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Controls&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;org.kde.kirigami&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;2.12&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;as&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Kirigami&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;QtQuick.Layouts&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;1.12&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;org.mydomain.simplemdviewer&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;1.0&lt;/span&gt;

&lt;span class="n"&gt;Kirigami&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ApplicationWindow&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;root&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;qsTr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Simple markdown viewer&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;minimumWidth&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Kirigami&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Units&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;gridUnit&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;20&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;minimumHeight&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Kirigami&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Units&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;gridUnit&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;20&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;pageStack&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;initialPage&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;initPage&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;Component&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;initPage&lt;/span&gt;

&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;Kirigami&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Page&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;qsTr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Markdown Viewer&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="n"&gt;MdConverter&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;mdconverter&lt;/span&gt;

&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="n"&gt;sourceText&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;sourceArea&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="n"&gt;ColumnLayout&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="n"&gt;anchors&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;                    &lt;/span&gt;&lt;span class="n"&gt;top&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;parent&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;top&lt;/span&gt;
&lt;span class="w"&gt;                    &lt;/span&gt;&lt;span class="n"&gt;left&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;parent&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;left&lt;/span&gt;
&lt;span class="w"&gt;                    &lt;/span&gt;&lt;span class="n"&gt;right&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;parent&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;right&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="n"&gt;Controls&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;TextArea&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;                    &lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;sourceArea&lt;/span&gt;

&lt;span class="w"&gt;                    &lt;/span&gt;&lt;span class="n"&gt;placeholderText&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;qsTr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Write here some markdown code&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;                    &lt;/span&gt;&lt;span class="n"&gt;wrapMode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Text&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WrapAnywhere&lt;/span&gt;
&lt;span class="w"&gt;                    &lt;/span&gt;&lt;span class="n"&gt;Layout&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;fillWidth&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;true&lt;/span&gt;
&lt;span class="w"&gt;                    &lt;/span&gt;&lt;span class="n"&gt;Layout&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;minimumHeight&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Kirigami&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Units&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;gridUnit&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="n"&gt;RowLayout&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;                    &lt;/span&gt;&lt;span class="n"&gt;Layout&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;fillWidth&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;true&lt;/span&gt;

&lt;span class="w"&gt;                    &lt;/span&gt;&lt;span class="n"&gt;Controls&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Button&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;                        &lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;qsTr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Format&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="w"&gt;                        &lt;/span&gt;&lt;span class="n"&gt;onClicked&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;formattedText&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;mdconverter&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;mdFormat&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="w"&gt;                    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="w"&gt;                    &lt;/span&gt;&lt;span class="n"&gt;Controls&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Button&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;                        &lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;qsTr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Clear&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="w"&gt;                        &lt;/span&gt;&lt;span class="n"&gt;onClicked&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;                            &lt;/span&gt;&lt;span class="n"&gt;sourceArea&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;                            &lt;/span&gt;&lt;span class="n"&gt;formattedText&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;                        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;                    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;

&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="n"&gt;Text&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;                    &lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;formattedText&lt;/span&gt;

&lt;span class="w"&gt;                    &lt;/span&gt;&lt;span class="n"&gt;textFormat&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Text&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;RichText&lt;/span&gt;
&lt;span class="w"&gt;                    &lt;/span&gt;&lt;span class="n"&gt;wrapMode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Text&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WordWrap&lt;/span&gt;

&lt;span class="w"&gt;                    &lt;/span&gt;&lt;span class="n"&gt;Layout&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;fillWidth&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;true&lt;/span&gt;
&lt;span class="w"&gt;                    &lt;/span&gt;&lt;span class="n"&gt;Layout&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;minimumHeight&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Kirigami&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Units&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;gridUnit&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;em class="blog-post-photo-centered"&gt;main.qml&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;The updated QML code:
1. imports the &lt;em&gt;org.mydomain.simplemdviewer&lt;/em&gt; library
2. creates an &lt;em&gt;MdConverter&lt;/em&gt; object
3. updates the &lt;em&gt;onClicked&lt;/em&gt; signal handler of the &lt;em&gt;Format&lt;/em&gt; button to call the &lt;em&gt;mdFormat&lt;/em&gt; function of the converter object&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Change directory to &lt;code&gt;simplemdviewer&lt;/code&gt; and execute:&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nx"&gt;python3&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;simplemdviewer_app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;py&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;ul&gt;
&lt;li&gt;Play with adding some markdown text:
&lt;img alt="Simple Markdown Viewer" class="blog-post-photo-centered-medium" src="https://dimitris.cc/images/simple-md-viewer-3.png"&gt;
&lt;em class="blog-post-photo-centered"&gt;Simple Markdown viewer in action&lt;/em&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Hooray.&lt;/p&gt;
&lt;h1&gt;Package the application&lt;/h1&gt;
&lt;p&gt;To distribute the application to the users we have to package it. We are going to use the &lt;em&gt;setuptools&lt;/em&gt; library. &lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Create an empty &lt;code&gt;__init__.py&lt;/code&gt; file in the &lt;code&gt;simplemdviewer&lt;/code&gt; directory. &lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This file is required to import a directory as a package. The project structure should look like this:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nx"&gt;simple&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;md&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;viewer&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;project&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;
&lt;span class="err"&gt;├──&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;
&lt;span class="err"&gt;└──&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;simplemdviewer&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="err"&gt;├──&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;py&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="err"&gt;├──&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;md_converter&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;py&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="err"&gt;├──&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;qml&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="err"&gt;└──&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;main&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;qml&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="err"&gt;└──&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;simplemdviewer_app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;py&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;ul&gt;
&lt;li&gt;Create a &lt;code&gt;pyproject.toml&lt;/code&gt; file in the &lt;code&gt;simple-md-viewer-project&lt;/code&gt; directory, to tell build tools what is needed to build our project:&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;[build-system]&lt;/span&gt;
&lt;span class="na"&gt;requires&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;[&amp;quot;setuptools&amp;quot;, &amp;quot;wheel&amp;quot;]&lt;/span&gt;
&lt;span class="na"&gt;build-backend&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;setuptools.build_meta&amp;quot;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;em class="blog-post-photo-centered"&gt;pyproject.toml&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Time to add the configuration file for setuptools. &lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Create a &lt;code&gt;setup.py&lt;/code&gt; into the &lt;code&gt;simple-md-viewer-project&lt;/code&gt; directory:&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;setuptools&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;setup&lt;/span&gt;
&lt;span class="n"&gt;setup&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;em class="blog-post-photo-centered"&gt;setup.py&lt;/em&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Add a new &lt;code&gt;setup.cfg&lt;/code&gt; file into the &lt;code&gt;simple-md-viewer-project&lt;/code&gt; directory:&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;[metadata]&lt;/span&gt;
&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;org.mydomain.simplemdviewer&lt;/span&gt;
&lt;span class="na"&gt;version&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;0.1&lt;/span&gt;
&lt;span class="na"&gt;url&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;https://mydomain.org/simplemdviewer&lt;/span&gt;
&lt;span class="na"&gt;author&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;Example Author&lt;/span&gt;
&lt;span class="na"&gt;author_email&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;example@author.org&lt;/span&gt;
&lt;span class="na"&gt;maintainer&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;Example Author&lt;/span&gt;
&lt;span class="na"&gt;maintainer_email&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;example@author.org&lt;/span&gt;
&lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;A simple markdown viewer&lt;/span&gt;
&lt;span class="na"&gt;long_description&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;file: README.md&lt;/span&gt;
&lt;span class="na"&gt;long_description_content_type&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;text/markdown&lt;/span&gt;
&lt;span class="na"&gt;classifiers&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="na"&gt;Development Status&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="s"&gt;: 5 - Production/Stable&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="na"&gt;License&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="s"&gt;: OSI Approved :: GNU General Public License v3 or later (GPLv3+)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="na"&gt;Intended Audience&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="s"&gt;: End Users/Desktop&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="na"&gt;Topic&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="s"&gt;: Utilities&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="na"&gt;Programming Language&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="s"&gt;: Python&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="na"&gt;Operating System&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="s"&gt;: POSIX :: Linux&lt;/span&gt;
&lt;span class="na"&gt;keywords&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;viewer converter markdown&lt;/span&gt;

&lt;span class="k"&gt;[options]&lt;/span&gt;
&lt;span class="na"&gt;packages&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;simplemdviewer&lt;/span&gt;
&lt;span class="na"&gt;include_package_data&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;True&lt;/span&gt;
&lt;span class="na"&gt;install_requires&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="na"&gt;requests&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="na"&gt;importlib; python_version &amp;gt;&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;3.8&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="na"&gt;markdown&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;em class="blog-post-photo-centered"&gt;setup.cfg&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;In the &lt;em&gt;metadata&lt;/em&gt; section we have provided information about the application. The &lt;em&gt;options&lt;/em&gt; section contains the project dependencies and the &lt;a href="https://packaging.python.org/en/latest/glossary/#term-Import-Package"&gt;import package&lt;/a&gt; that our &lt;a href="https://packaging.python.org/en/latest/glossary/#term-Distribution-Package"&gt;distribution package&lt;/a&gt; is going to provide. There is a lot of &lt;a href="https://setuptools.readthedocs.io/en/latest/userguide/declarative_config.html"&gt;options&lt;/a&gt; and &lt;a href="https://pypi.org/classifiers/"&gt;classifiers&lt;/a&gt; available. For more details on dependencies management in &lt;em&gt;setuptools&lt;/em&gt;, check &lt;a href="https://setuptools.pypa.io/en/latest/userguide/dependency_management.html"&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;It is good to have a &lt;code&gt;README.md&lt;/code&gt; file as well. &lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Create a &lt;code&gt;README.md&lt;/code&gt; file in the &lt;code&gt;simple-md-viewer-project&lt;/code&gt; directory:&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Simple&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Markdown&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Viewer&lt;/span&gt;

&lt;span class="nx"&gt;A&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;simple&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;markdown&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;viewer&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;created&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;with&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Kirigami&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;QML&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;and&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Python&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;em class="blog-post-photo-centered"&gt;README.md&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Another important piece is the license of our project.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Create a &lt;code&gt;LICENCE.txt&lt;/code&gt; file and add the text of the &lt;a href="https://www.gnu.org/licenses/gpl-3.0.txt"&gt;license&lt;/a&gt; of our project.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Apart from the Python stuff, we have to add the QML code to the distribution package as well.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Create a &lt;code&gt;MANIFEST.in&lt;/code&gt; file in the &lt;code&gt;simple-md-viewer-project&lt;/code&gt; directory, and add:&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nx"&gt;include&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;simplemdviewer&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;qml&lt;/span&gt;&lt;span class="o"&gt;/*&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;qml&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;em class="blog-post-photo-centered"&gt;MANIFEST.in&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Some last pieces and we are ready to build. We are going to add:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;the &lt;a href="https://www.freedesktop.org/wiki/Distributions/AppStream/"&gt;appstream&lt;/a&gt; metadata to be used by the software centers&lt;/li&gt;
&lt;li&gt;a &lt;a href="https://specifications.freedesktop.org/desktop-entry-spec/desktop-entry-spec-latest.html"&gt;desktop entry&lt;/a&gt; file to add the application to the application launcher&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;an application icon &lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Create a new &lt;code&gt;org.mydomain.simplemdviewer.desktop&lt;/code&gt; file into the &lt;code&gt;simple-md-viewer-project&lt;/code&gt; directory:&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;[Desktop Entry]&lt;/span&gt;
&lt;span class="na"&gt;Version&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;0.1&lt;/span&gt;
&lt;span class="na"&gt;Type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;Application&lt;/span&gt;
&lt;span class="na"&gt;Name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;Simple Markdown Viewer&lt;/span&gt;
&lt;span class="na"&gt;Name&lt;/span&gt;&lt;span class="nn"&gt;[x-test]&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;xxSimple Markdown Viewerxx&lt;/span&gt;
&lt;span class="na"&gt;GenericName&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;Markdown Viewer&lt;/span&gt;
&lt;span class="na"&gt;GenericName&lt;/span&gt;&lt;span class="nn"&gt;[x-test]&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;xxMarkdown Viewerxx&lt;/span&gt;
&lt;span class="na"&gt;Comment&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;A simple markdown viewer application&lt;/span&gt;
&lt;span class="na"&gt;Comment&lt;/span&gt;&lt;span class="nn"&gt;[x-test]&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;xxA simple markdown viewer applicationxx&lt;/span&gt;
&lt;span class="na"&gt;Exec&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;simplemdviewer&lt;/span&gt;
&lt;span class="na"&gt;Icon&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;org.mydomain.simplemdviewer&lt;/span&gt;
&lt;span class="na"&gt;Terminal&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;false&lt;/span&gt;
&lt;span class="na"&gt;Categories&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;Office;&lt;/span&gt;
&lt;span class="na"&gt;X-KDE-FormFactor&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;desktop;tablet;handset;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;em class="blog-post-photo-centered"&gt;org.mydomain.simplemdviewer.desktop&lt;/em&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Add a new &lt;code&gt;org.mydomain.simplemdviewer.metainfo.xml&lt;/code&gt; file into the &lt;code&gt;simple-md-viewer-project&lt;/code&gt; directory:&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;?xml version=&amp;quot;1.0&amp;quot; encoding=&amp;quot;utf-8&amp;quot;?&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;component&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;desktop&amp;quot;&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;id&amp;gt;&lt;/span&gt;org.mydomain.simplemdviewer&lt;span class="nt"&gt;&amp;lt;/id&amp;gt;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;metadata_license&amp;gt;&lt;/span&gt;CC0-1.0&lt;span class="nt"&gt;&amp;lt;/metadata_license&amp;gt;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;project_license&amp;gt;&lt;/span&gt;GPL-3.0+&lt;span class="nt"&gt;&amp;lt;/project_license&amp;gt;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;name&amp;gt;&lt;/span&gt;Simple&lt;span class="w"&gt; &lt;/span&gt;Markdown&lt;span class="w"&gt; &lt;/span&gt;Viewer&lt;span class="nt"&gt;&amp;lt;/name&amp;gt;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;summary&amp;gt;&lt;/span&gt;A&lt;span class="w"&gt; &lt;/span&gt;simple&lt;span class="w"&gt; &lt;/span&gt;markdown&lt;span class="w"&gt; &lt;/span&gt;viewer&lt;span class="w"&gt; &lt;/span&gt;application&lt;span class="nt"&gt;&amp;lt;/summary&amp;gt;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;description&amp;gt;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;p&amp;gt;&lt;/span&gt;Simple&lt;span class="w"&gt; &lt;/span&gt;Markdown&lt;span class="w"&gt; &lt;/span&gt;Viewer&lt;span class="w"&gt; &lt;/span&gt;is&lt;span class="w"&gt; &lt;/span&gt;a&lt;span class="w"&gt; &lt;/span&gt;showcase&lt;span class="w"&gt; &lt;/span&gt;application&lt;span class="w"&gt; &lt;/span&gt;for&lt;span class="w"&gt; &lt;/span&gt;QML&lt;span class="w"&gt; &lt;/span&gt;with&lt;span class="w"&gt; &lt;/span&gt;Python&lt;span class="w"&gt; &lt;/span&gt;development&lt;span class="nt"&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;/description&amp;gt;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;url&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;homepage&amp;quot;&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;https://mydomain.org/simplemdviewer&lt;span class="nt"&gt;&amp;lt;/url&amp;gt;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;releases&amp;gt;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;release&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="na"&gt;version=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;0.1&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="na"&gt;date=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;2022-02-25&amp;quot;&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;description&amp;gt;&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;p&amp;gt;&lt;/span&gt;First&lt;span class="w"&gt; &lt;/span&gt;release&lt;span class="nt"&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;/description&amp;gt;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;/release&amp;gt;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;/releases&amp;gt;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;provides&amp;gt;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;binary&amp;gt;&lt;/span&gt;simplemdviewer&lt;span class="nt"&gt;&amp;lt;/binary&amp;gt;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;/provides&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/component&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;em class="blog-post-photo-centered"&gt;org.mydomain.simplemdviewer.metainfo.xml&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;For our tutorial the well known markdown icon is OK.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Get the &lt;a href="https://en.wikipedia.org/wiki/Markdown#/media/File:Markdown-mark.svg"&gt;markdown&lt;/a&gt; icon and save it as &lt;code&gt;org.mydomain.simplemdviewer.svg&lt;/code&gt; into the &lt;code&gt;simple-md-viewer-project&lt;/code&gt; directory.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Now we have to let &lt;code&gt;setup.cfg&lt;/code&gt; know about the new files. Let's also provide an easy way to open the application from the console by just typing &lt;code&gt;simplemdviewer&lt;/code&gt;.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Update the &lt;code&gt;setup.cfg&lt;/code&gt; to:&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;[metadata]&lt;/span&gt;
&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;org.mydomain.simplemdviewer&lt;/span&gt;
&lt;span class="na"&gt;version&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;0.1&lt;/span&gt;
&lt;span class="na"&gt;url&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;https://mydomain.org/simplemdviewer&lt;/span&gt;
&lt;span class="na"&gt;author&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;Example Author&lt;/span&gt;
&lt;span class="na"&gt;author_email&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;example@author.org&lt;/span&gt;
&lt;span class="na"&gt;maintainer&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;Example Author&lt;/span&gt;
&lt;span class="na"&gt;maintainer_email&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;example@author.org&lt;/span&gt;
&lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;A simple markdown viewer&lt;/span&gt;
&lt;span class="na"&gt;long_description&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;file: README.md&lt;/span&gt;
&lt;span class="na"&gt;long_description_content_type&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;text/markdown&lt;/span&gt;
&lt;span class="na"&gt;classifiers&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="na"&gt;Development Status&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="s"&gt;: 5 - Production/Stable&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="na"&gt;License&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="s"&gt;: OSI Approved :: GNU General Public License v3 or later (GPLv3+)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="na"&gt;Intended Audience&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="s"&gt;: End Users/Desktop&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="na"&gt;Topic&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="s"&gt;: Utilities&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="na"&gt;Programming Language&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="s"&gt;: Python&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="na"&gt;Operating System&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="s"&gt;: POSIX :: Linux&lt;/span&gt;
&lt;span class="na"&gt;keywords&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;viewer converter markdown&lt;/span&gt;

&lt;span class="k"&gt;[options]&lt;/span&gt;
&lt;span class="na"&gt;packages&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;simplemdviewer&lt;/span&gt;
&lt;span class="na"&gt;include_package_data&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;True&lt;/span&gt;
&lt;span class="na"&gt;install_requires&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="na"&gt;requests&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="na"&gt;importlib; python_version &amp;gt;&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;3.8&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="na"&gt;markdown&lt;/span&gt;

&lt;span class="k"&gt;[options.data_files]&lt;/span&gt;
&lt;span class="na"&gt;share/applications&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="na"&gt;org.mydomain.simplemdviewer.desktop&lt;/span&gt;
&lt;span class="na"&gt;share/icons/hicolor/scalable/apps&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="na"&gt;org.mydomain.simplemdviewer.svg&lt;/span&gt;
&lt;span class="na"&gt;share/metainfo&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;
&lt;span class="w"&gt;     &lt;/span&gt;&lt;span class="na"&gt;org.mydomain.simplemdviewer.metainfo.xml&lt;/span&gt;

&lt;span class="k"&gt;[options.entry_points]&lt;/span&gt;
&lt;span class="na"&gt;console_scripts&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="na"&gt;simplemdviewer&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;simplemdviewer.simplemdviewer_app:main&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;em class="blog-post-photo-centered"&gt;setup.cfg (updated)&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Last step is to tinker with the way we import modules.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Update the &lt;code&gt;simplemdviewer_app.py&lt;/code&gt; file to:&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;os&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;PySide2.QtGui&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;QGuiApplication&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;PySide2.QtCore&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;QUrl&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;PySide2.QtQml&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;QQmlApplicationEngine&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;qmlRegisterType&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;simplemdviewer.md_converter&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;MdConverter&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&amp;quot;&amp;quot;Initializes and manages the application execution&amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;QGuiApplication&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;engine&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;QQmlApplicationEngine&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;qmlRegisterType&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;MdConverter&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;org.mydomain.simplemdviewer&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;                    &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;MdConverter&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;base_path&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;abspath&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;dirname&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="vm"&gt;__file__&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;QUrl&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;file://&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;base_path&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s1"&gt;/qml/main.qml&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;engine&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;load&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;engine&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;rootObjects&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;quit&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;exec_&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="vm"&gt;__name__&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;__main__&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;em class="blog-post-photo-centered"&gt;simplemdviewer_app.py (updated)&lt;/em&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Create a &lt;code&gt;__main__.py&lt;/code&gt; file into the &lt;code&gt;simplemdviewer&lt;/code&gt; directory:&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;.&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;simplemdviewer_app&lt;/span&gt;

&lt;span class="n"&gt;simplemdviewer_app&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;This last step will facilitate the excecution of the package during development.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Execute from inside the &lt;code&gt;simple-md-viewer-project&lt;/code&gt; a last check before packaging:&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nx"&gt;python3&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;m&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;simplemdviewer&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;It's time to generate the &lt;em&gt;distribution package&lt;/em&gt; for our &lt;em&gt;import package&lt;/em&gt;.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Make sure that the latest version of &lt;code&gt;build&lt;/code&gt; is installed:&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;python3 -m pip install --upgrade build
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;ul&gt;
&lt;li&gt;Execute from inside the &lt;code&gt;simple-md-viewer-project&lt;/code&gt; directory:&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;python3 -m build
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;As soon as the build completes, two archives will be created in the &lt;code&gt;dist&lt;/code&gt; directory:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;the &lt;code&gt;org.mydomain.simplemdviewer-0.1.tar.gz&lt;/code&gt; source archive&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;the &lt;code&gt;org.mydomain.simplemdviewer-0.1-py3-none-any.whl&lt;/code&gt; &lt;a href="https://packaging.python.org/en/latest/glossary/#term-Built-Distribution"&gt;built distribution&lt;/a&gt; package&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Install the package into the virtual environment:&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nx"&gt;python3&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;m&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;pip&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;install&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;dist&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;org&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;mydomain&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;simplemdviewer&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="m m-Double"&gt;0.1&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tar&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;gz&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;ul&gt;
&lt;li&gt;Run:&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nx"&gt;simplemdviewer&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;At this point we can tag and release the source code. The Linux distributions will package it and the application will be added to their software repositories.&lt;/p&gt;
&lt;p&gt;Well done.&lt;/p&gt;
&lt;p&gt;We can also take care of distributing it. Although the &lt;a href="https://pypi.org/"&gt;PyPI&lt;/a&gt; is probably the standard way to distribute a Python package, I believe that &lt;a href="https://flatpak.org/"&gt;Flatpak&lt;/a&gt; is more compatible with the Linux applications ecoystem.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Create an &lt;code&gt;org.mydomain.simplemdviewer.json&lt;/code&gt; file in the &lt;code&gt;simple-md-viewer-project&lt;/code&gt; directory:&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;id&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;org.mydomain.simplemdviewer&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;runtime&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;org.kde.Platform&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;runtime-version&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;5.15&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;sdk&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;org.kde.Sdk&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;command&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;simplemdviewer&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;finish-args&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;--share=ipc&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;--socket=fallback-x11&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;--socket=wayland&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;--device=dri&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;--socket=pulseaudio&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;modules&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;python3-markdown.json&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;name&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;PySide2&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;buildsystem&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;cmake-ninja&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;builddir&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="bp"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;config-opts&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;-DCMAKE_BUILD_TYPE=Release&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;-DBUILD_TESTS=OFF&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;sources&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;                    &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;type&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;archive&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;                    &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;url&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;https://download.qt.io/official_releases/QtForPython/pyside2/PySide2-5.15.2-src/pyside-setup-opensource-src-5.15.2.tar.xz&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;                    &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;sha256&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;b306504b0b8037079a8eab772ee774b9e877a2d84bab2dbefbe4fa6f83941418&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;                    &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;type&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;shell&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;                    &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;commands&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;
&lt;span class="w"&gt;                        &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;mkdir -p /app/include/qt5tmp &amp;amp;&amp;amp; cp -R /usr/include/Qt* /app/include/qt5tmp # https://bugreports.qt.io/broswse/PYSIDE-787&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;                        &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;sed -i &amp;#39;s|--include-paths=|--include-paths=/app/include/qt5tmp:|&amp;#39; sources/pyside2/cmake/Macros/PySideModules.cmake&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;                    &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;name&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;simplemdviewer&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;buildsystem&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;simple&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;build-commands&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;python3 setup.py build&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;python3 setup.py install --prefix=/app --root=/&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;sources&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;                    &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;type&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;archive&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;                    &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;path&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;dist/org.mydomain.simplemdviewer-0.1.tar.gz&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;em class="blog-post-photo-centered"&gt;org.mydomain.simplemdviewer.json&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;This file reads that we use the &lt;em&gt;markdown&lt;/em&gt; module and the build info is provided by the &lt;code&gt;python3-markdown.json&lt;/code&gt; manifest file. We are going to create this manifest using &lt;code&gt;flatpak-pip-generator&lt;/code&gt;.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Get the &lt;a href="https://raw.githubusercontent.com/flatpak/flatpak-builder-tools/master/pip/flatpak-pip-generator"&gt;flatpak-pip-generator&lt;/a&gt;, save it into the &lt;code&gt;env/bin/&lt;/code&gt; directory, and run:&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;python3 env/bin/flatpak-pip-generator markdown
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;ul&gt;
&lt;li&gt;Install &lt;em&gt;org.kde.Sdk&lt;/em&gt; and &lt;em&gt;org.kde.Platform&lt;/em&gt;, version 5.15, from flathub:&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;flatpak install org.kde.Platform/x86_64/5.15 org.kde.Sdk/x86_64/5.15
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;ul&gt;
&lt;li&gt;Install the &lt;em&gt;flatpak-builder&lt;/em&gt; package using the package manager of your distribution and run:&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nx"&gt;flatpak&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;builder&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;--&lt;/span&gt;&lt;span class="nx"&gt;verbose&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;--&lt;/span&gt;&lt;span class="nx"&gt;force&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;clean&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;flatpak&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;build&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;dir&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;org&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;mydomain&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;simplemdviewer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;json&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;ul&gt;
&lt;li&gt;Test the flatpak build:&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nx"&gt;flatpak&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;builder&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;--&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="nx"&gt;QT_QPA_PLATFORM&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="nx"&gt;wayland&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;--&lt;/span&gt;&lt;span class="nx"&gt;run&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;flatpak&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;build&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;dir&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;org&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;mydomain&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;simplemdviewer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;json&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;simplemdviewer&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;ul&gt;
&lt;li&gt;Build the flatpak bundle:&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nx"&gt;flatpak&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;builder&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;--&lt;/span&gt;&lt;span class="nx"&gt;install&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;deps&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;from&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="nx"&gt;flathub&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;flatpak&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;build&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;dir&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;--&lt;/span&gt;&lt;span class="nx"&gt;repo&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="nx"&gt;simplemdviewer&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;master&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;--&lt;/span&gt;&lt;span class="nx"&gt;force&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;clean&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;--&lt;/span&gt;&lt;span class="nx"&gt;ccache&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;org&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;mydomain&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;simplemdviewer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;json&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;
&lt;span class="nx"&gt;flatpak&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;build&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;bundle&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;simplemdviewer&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;master&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;simplemdviewer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;flatpak&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;org&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;mydomain&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;simplemdviewer&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Now we can either distribute &lt;code&gt;simplemdviewer.flatpak&lt;/code&gt; directly to the users, or submit the application to a flatpak repository, e.g., on &lt;a href="https://github.com/flathub/flathub/wiki/App-Submission"&gt;flathub&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Great.&lt;/p&gt;
&lt;p&gt;I suggest that you also consider:
- Signing the source archive with a &lt;a href="https://www.gnupg.org/gph/en/manual/x135.html"&gt;detached signature&lt;/a&gt;
- Providing copyright and licensing information for each file with &lt;a href="https://github.com/fsfe/reuse-tool"&gt;reuse&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;The code of the article can be found &lt;a href="https://codeberg.org/dimkard/simple-md-viewer-project"&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;If you have found this article interesting, share it with your friends on your favorite social platform. I hope this platform is &lt;a href="https://joinmastodon.org/"&gt;Mastodon&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Happy hacking.&lt;/p&gt;</content><category term="kde"></category></entry><entry><title>Glossaico 1.0 release</title><link href="https://dimitris.cc/posts/glossaico-10-release.html" rel="alternate"></link><published>2021-12-02T10:00:00+02:00</published><updated>2021-12-02T10:00:00+02:00</updated><author><name>Dimitris</name></author><id>tag:dimitris.cc,2021-12-02:/posts/glossaico-10-release.html</id><summary type="html">&lt;p&gt;The first stable version of Glossaico has been released.&lt;/p&gt;
&lt;p&gt;Glossaico is a language learning application based on &lt;a href="https://librelingo.app"&gt;LibreLingo&lt;/a&gt;, created for mobile devices running Linux. However, you can also execute it on any environment where Python, Qt and Kirigami are available.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Glossaico" src="https://dimitris.cc/images/glossaico-pm.png"&gt;
&lt;em class="blog-post-photo-centered"&gt;Glossaico on Plasma Mobile&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;This release offers three language courses …&lt;/p&gt;</summary><content type="html">&lt;p&gt;The first stable version of Glossaico has been released.&lt;/p&gt;
&lt;p&gt;Glossaico is a language learning application based on &lt;a href="https://librelingo.app"&gt;LibreLingo&lt;/a&gt;, created for mobile devices running Linux. However, you can also execute it on any environment where Python, Qt and Kirigami are available.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Glossaico" src="https://dimitris.cc/images/glossaico-pm.png"&gt;
&lt;em class="blog-post-photo-centered"&gt;Glossaico on Plasma Mobile&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;This release offers three language courses:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Spanish for English speakers&lt;/li&gt;
&lt;li&gt;German for English speakers&lt;/li&gt;
&lt;li&gt;Basque for English speakers&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Archives and signatures are available &lt;a href="https://codeberg.org/dimkard/glossaico/releases/tag/v1.0"&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;h1&gt;Install&lt;/h1&gt;
&lt;p&gt;A flatpak build of Glossaico is available on &lt;a href="https://flathub.org/apps/details/org.codeberg.dimkard.glossaico"&gt;flathub&lt;/a&gt;. If the software center of your distribution supports flatpak and the flathub repository has been configured, just use it to install Glossaico. If you prefer the command line, execute &lt;code&gt;flatpak install flathub org.codeberg.dimkard.glossaico&lt;/code&gt;.&lt;/p&gt;
&lt;h1&gt;Build&lt;/h1&gt;
&lt;p&gt;To tinker with the code or build the application package directly from source, please install PySide2 and Kirigami using the package manager of your distribution. Then, extract the release archive and execute:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;python3 -m build
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;After building, you will find the distribution files in the &lt;code&gt;dist&lt;/code&gt; directory. Then, install the application:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;pip install dist/glossaico-1.0.tar.gz
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h1&gt;Report bugs&lt;/h1&gt;
&lt;p&gt;Please report issues and provide your ideas on the &lt;a href="https://codeberg.org/dimkard/glossaico/issues"&gt;bug tracker&lt;/a&gt; of the application.&lt;/p&gt;
&lt;h1&gt;Get involved&lt;/h1&gt;
&lt;p&gt;The &lt;a href="https://github.com/kantord/LibreLingo"&gt;LibreLingo&lt;/a&gt; platform and the language courses have been created and are maintained by a friendly community of volunteers. Feel free to join us on &lt;a href="https://matrix.to/#/#librelingo:matrix.org"&gt;matrix&lt;/a&gt; and follow us on &lt;a href="https://fosstodon.org/@librelingo"&gt;Mastodon&lt;/a&gt;. With regards to Glossaico, I would be glad to review your pull requests on &lt;a href="https://codeberg.org/dimkard/glossaico"&gt;codeberg&lt;/a&gt;.&lt;/p&gt;</content><category term="general"></category></entry><entry><title>Glossaico 1.0 beta release</title><link href="https://dimitris.cc/posts/glossaico-10-beta-release.html" rel="alternate"></link><published>2021-11-07T21:00:00+02:00</published><updated>2021-11-07T21:00:00+02:00</updated><author><name>Dimitris</name></author><id>tag:dimitris.cc,2021-11-07:/posts/glossaico-10-beta-release.html</id><summary type="html">&lt;p&gt;The first beta release of Glossaico 1.0 is out for early adopters, testers and linux-on-mobile enthusiasts. Glossaico is a language learning application based on &lt;a href="https://github.com/kantord/LibreLingo"&gt;LibreLingo&lt;/a&gt;, a community-owned language-learning platform.&lt;/p&gt;
&lt;p&gt;Glossaico has been created for mobile devices running Linux, but the technology stack it makes use of -Python and Qt- …&lt;/p&gt;</summary><content type="html">&lt;p&gt;The first beta release of Glossaico 1.0 is out for early adopters, testers and linux-on-mobile enthusiasts. Glossaico is a language learning application based on &lt;a href="https://github.com/kantord/LibreLingo"&gt;LibreLingo&lt;/a&gt;, a community-owned language-learning platform.&lt;/p&gt;
&lt;p&gt;Glossaico has been created for mobile devices running Linux, but the technology stack it makes use of -Python and Qt- does not prevent it from running on other environments as well.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Glossaico" src="https://dimitris.cc/images/glossaico-pm.png"&gt;
&lt;em class="blog-post-photo-centered"&gt;Glossaico on Plasma Mobile&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;At the moment, it contains three language courses:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Spanish for English speakers&lt;/li&gt;
&lt;li&gt;German for English speakers&lt;/li&gt;
&lt;li&gt;Basque for English speakers&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Archives and signatures are available &lt;a href="https://codeberg.org/dimkard/glossaico/releases/tag/v1.0-beta"&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;h1&gt;Build and install&lt;/h1&gt;
&lt;p&gt;First, install PySide2 and Kirigami2 using the package manager of your distribution. Then, extract the beta release archive and execute:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;python3 -m build
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;After building, in the &lt;code&gt;dist&lt;/code&gt; directory you can find the distribution files. You can now install the application:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;pip install dist/glossaico-1.0b0.tar.gz
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h1&gt;Feedback&lt;/h1&gt;
&lt;p&gt;Provide your feedback and report issues on the &lt;a href="https://codeberg.org/dimkard/glossaico/issues"&gt;bug tracker&lt;/a&gt; of the project.&lt;/p&gt;</content><category term="general"></category></entry><entry><title>Is the medium the message?</title><link href="https://dimitris.cc/posts/is-the-medium-the-message.html" rel="alternate"></link><published>2021-05-30T17:30:00+03:00</published><updated>2021-05-30T17:30:00+03:00</updated><author><name>Dimitris</name></author><id>tag:dimitris.cc,2021-05-30:/posts/is-the-medium-the-message.html</id><summary type="html">&lt;p&gt;Amidst the covid19 pandemic, almost all conferences and meetings have been taking place online. Among them, there is a series of events held by free software (foss) communities, social and solidarity economy cooperatives and groups that defend human rights. Throughout this post, I will refer to them as TGFs (the …&lt;/p&gt;</summary><content type="html">&lt;p&gt;Amidst the covid19 pandemic, almost all conferences and meetings have been taking place online. Among them, there is a series of events held by free software (foss) communities, social and solidarity economy cooperatives and groups that defend human rights. Throughout this post, I will refer to them as TGFs (the good folks). No doubt, a critical part of an online conference is the bundle of platforms and tools that power the conference - from registration and presentation to interaction and streaming. Just a moment, is it really a critical part?&lt;/p&gt;
&lt;p&gt;Looking into the set of tools adopted by many TGFs you may deduce that the medium does not matter. In many cases, proprietary tools and platforms notorious for censoring and abusive moderation have been used. So, the end justify the means, isn't it? People will eventually judge us by the great things we are going to say and do during an event, not by the medium we have chosen, right?&lt;/p&gt;
&lt;p&gt;First, the end does not justify the means; this is a slippery slope that in many cases throughout the history of the humankind has led well-intention causes to catastrophic results. But let's avoid dramas and too much of an abstraction.&lt;/p&gt;
&lt;p&gt;We have been educated for years to use software and commercial platforms that do not respect our rights; the promotion and marketing power of the companies that create these tools is huge. At the same time, a TGF that adopts such a kind of software legitimizes their use and sends a wrong message to the people. You trust a group and, as a consequence, you may trust the tools that this group uses. Certainly, just a single TGF has not enough power to whitewash an unethical platform but what about a large set of well trusted groups that simultaneously send this "we use it - you trust us - you trust it" message?&lt;/p&gt;
&lt;p&gt;The process of de-educating ourselves and working on our digital emancipation is not a simple task; it needs a lot of will, effort and time to spend. In many cases, the "bad" software has been cleverly designed to lock-in us, making the way out a painful process. But if we want to break free, someone has to start by adopting and promoting the use of ethical alternatives. Is it a task that should be assigned to a TGF as part of its next online conference?&lt;/p&gt;
&lt;p&gt;Although I am tempted to just say "yes, it is", life is complicated and full of contradictions and compromises. There is slew of factors that we should take into account. The time available to organize an event varies as well as the amount of people involved. The same applies to the technical expertise of the organizers. Some groups may also have financial resources to spend on software tools while others may -or may not- can ask help from tech savvy friends. So, let's assume that a TGF is going to organize an online event, there is also a reasonable amount of time and volunteers available as well as technical expertise -within the groups or friends- or enough money to spend on software tools. Should this TGF use only free software to do the job?&lt;/p&gt;
&lt;p&gt;Again, I would avoid to answer "yes, just use foss". Instead, I would examine the unethical tools (if any) that they are about to employ, the way that they are going to use them, the existence of alternative options to conference participants that do not feel comfortable with these tools, and finally, the goal that they are trying to achieve. Let's examine some usual suspects in a real life scenario: a beloved organization that is going to hold an online conference powered by some not-so-beloved tools like Facebook, Zoom and Youtube.&lt;/p&gt;
&lt;p&gt;Facebook does not need further introductions. Its business model is based on tracking, analyzing and modeling users' behavior and it has repeatedly suspended or even deleted accounts that talk about things that do not belong in the for-profit agenda of the company. So, dear TGF, don't use it. No, it's not that easy. The huge userbase of Facebook is quite important for many groups to ignore. Although I believe that it does more harm than good, I understand why groups I do respect cannot avoid using it. From their viewpoint, Facebook is a tool to send the message to a large audience. &lt;/p&gt;
&lt;p&gt;However, details matter. E.g, what Facebook features will be used? "Facebook Live" for streaming, "Groups" for internal interactions, "Events" for the conference scheduling? All of them? Most importantly, is Facebook going to be the only platform for all these tasks or will we provide alternatives? To put it simply, are we going to exclude people without a Facebook account?&lt;/p&gt;
&lt;p&gt;The question of alternatives-or-exclusion also applies to the Youtube case. Fortunately, Youtube streaming can easily- or less easily- be bypassed by tech savvy participants. Nevertheless, the comments is a whole other story. If we let the conference participants to feed the greedy algorithms of Google, while excluding non-Google users or encouraging people to sign up to theses platforms in the name of a "good message", is the message still a good one?&lt;/p&gt;
&lt;p&gt;I suppose that behind the imperative of Facebook and Youtube services lies the lust for big numbers. There is a fallacy that Facebook group/event members or Youtube views is the absolute metric to assess a successful community and the events it organizes. Certainly, quantity is important, but it depends on quality and it also affects quality. E.g. 100 twitter -Elon Musk groupie style- followers is as valuable as 20 Mastodon ones -sometimes derogatorily called "our bubble"? More importantly, TGFs do not only influence others but they are also influenced by others. This is not bad at all. We may opt to choose solely the bad mediums because the good ones are full of our friends which have already been loving us, and increasingly adapt ourselves to the particularities of the commercial platforms to spread our clean and pure message. In the end, our old friends may not love us anymore because we have just got rid of the G of our acronym.&lt;/p&gt;
&lt;p&gt;There is also the case of Zoom, a word used as a synonym for "video-conference". Zoom is a platform that has lied about using end-to-end encryption and censored meetings of progressive people. Speakers privacy may not seem relevant for a public conference; however, the adoption of Zoom sends a really bad message. How can we care for privacy and free speech while encouraging people to use a platform that does not care for human rights? Ethical alternatives like Jitsi are quite simple for small events. On the other hand, large events like Remote Chaos Experience or FOSDEM have also been powered by free software tools confirming that conferences of thousands of participants are feasible without resorting to commercial platforms.&lt;/p&gt;
&lt;p&gt;Free software tools and decentralized platforms are important neither because they make us look nice -if we adopt them- nor because that's the way to make our grumpy digital vegan friends stop complaining. These tools and platforms ensure the very existence of freedom of speech and foster participation and inclusivity. They are based on interoperability protocols, preventing users from being locked-in and, as a result, abused by powerful actors. &lt;/p&gt;
&lt;p&gt;We may -or may not- keep on using Facebook and the like to reach out to a larger audience. If we opt to do so, we should treat them the way they deserve: secondary and complementary tools we are "forced" to use because of network effects. &lt;/p&gt;
&lt;p&gt;In conclusion, I tend to trust the tech savvy communities, groups and organizations that put effort on de-educating and re-educating by example the digital citizens by opting for ethical platforms. I also tend to trust the non-tech groups that understand the importance of the tools they use, and ask for help. However, the case of Facebook is kind of special. Personally, I understand why trusted groups still use Facebook as an outreach tool, and I still trust them as soon as they also offer alternative means to avoid excluding non-facebook users. In particular, I truly respect the groups that even if they use the unethical, commercial platforms, they do not compromise; they do not adopt "happy facebook personas" to spread the word, and keep on criticizing censorship and the obscure nature of facebook algorithms, even if these actions put themselves in danger of being suspended or blocked.&lt;/p&gt;</content><category term="general"></category></entry><entry><title>Kongress 1.0.1 is available</title><link href="https://dimitris.cc/posts/kongress-101-is-available.html" rel="alternate"></link><published>2021-03-09T09:40:00+02:00</published><updated>2021-03-09T09:40:00+02:00</updated><author><name>Dimitris</name></author><id>tag:dimitris.cc,2021-03-09:/posts/kongress-101-is-available.html</id><summary type="html">&lt;p&gt;The first bug fix release of Kongress, the conference companion application of the KDE community, is now available. Several fixes have been included in this release:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The time displayed on talk notifications has been corrected&lt;/li&gt;
&lt;li&gt;Several crash scenarios of kongressac, the alarm checker of kongress, have been addressed&lt;/li&gt;
&lt;li&gt;Corner cases …&lt;/li&gt;&lt;/ul&gt;</summary><content type="html">&lt;p&gt;The first bug fix release of Kongress, the conference companion application of the KDE community, is now available. Several fixes have been included in this release:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The time displayed on talk notifications has been corrected&lt;/li&gt;
&lt;li&gt;Several crash scenarios of kongressac, the alarm checker of kongress, have been addressed&lt;/li&gt;
&lt;li&gt;Corner cases of incorrect grouping of talks into conference days have been fixed&lt;/li&gt;
&lt;li&gt;No duplicate conference entries are being displayed anymore&lt;/li&gt;
&lt;li&gt;Generic, multi-day calendar events are not included in the per-day pages&lt;/li&gt;
&lt;li&gt;Translation configuration has been fixed&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img alt="Kongress mobile" class="blog-post-photo-centered-medium" src="https://dimitris.cc/images/kongress_fossasia_2021.png"&gt;
&lt;em class="blog-post-photo-centered"&gt;FOSSASIA 2021 schedule on Plasma Mobile&lt;/em&gt;&lt;/p&gt;
&lt;h1&gt;Installation&lt;/h1&gt;
&lt;p&gt;Kongress is available in various desktop and mobile Linux distributions. You can also install the flatpak version of Kongress from the software repository of your distribution or directly from &lt;a href="https://flathub.org/apps/details/org.kde.kongress"&gt;flathub&lt;/a&gt;. Android users can also try the nightly build from the &lt;a href="https://community.kde.org/Android/FDroid"&gt;F-Droid repository&lt;/a&gt; of KDE.&lt;/p&gt;
&lt;h1&gt;Source code&lt;/h1&gt;
&lt;p&gt;The source code and signatures can be downloaded from &lt;a href="https://download.kde.org/stable/kongress/1.0.1/"&gt;download.kde.org&lt;/a&gt;.&lt;/p&gt;
&lt;h1&gt;Feedback&lt;/h1&gt;
&lt;p&gt;Feel free to provide your feedback and follow the discussion about its development on the &lt;a href="https://matrix.to/#/#plasmamobile:matrix.org"&gt;matrix channel&lt;/a&gt; of Plasma Mobile.&lt;/p&gt;</content><category term="kde"></category></entry><entry><title>Kongress 1.0 release</title><link href="https://dimitris.cc/posts/kongress-10-release.html" rel="alternate"></link><published>2021-01-28T18:50:00+02:00</published><updated>2021-01-28T18:50:00+02:00</updated><author><name>Dimitris</name></author><id>tag:dimitris.cc,2021-01-28:/posts/kongress-10-release.html</id><summary type="html">&lt;p&gt;I am pleased to announce that Kongress 1.0 has been released. Kongress is a conference companion application enabling users to organize their participation in conferences. &lt;/p&gt;
&lt;p&gt;The first release of Kongress offers the following features:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;The schedule of a collection of free software conferences, displayed in various ways: full, by …&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;</summary><content type="html">&lt;p&gt;I am pleased to announce that Kongress 1.0 has been released. Kongress is a conference companion application enabling users to organize their participation in conferences. &lt;/p&gt;
&lt;p&gt;The first release of Kongress offers the following features:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;The schedule of a collection of free software conferences, displayed in various ways: full, by day, or by category - if the conference is organized in different tracks.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;The scheduled time of each talk shown either in the organizer's or local time zone.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Users can maintain a list of favorite talks per conference.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;A notification can be displayed before the beginning of a favorite talk. At the moment, notifications are supported only on Plasma 5.20 (or later).&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The primary target user-base of Kongress is people attending a conference with a Linux mobile phone. It also works on desktop, leveraging the convergence capabilities of &lt;a href="https://develop.kde.org/frameworks/kirigami//"&gt;Kirigami&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Kongress mobile" class="blog-post-photo-centered-medium" src="https://dimitris.cc/images/kongress_fosdem_2021.png"&gt;
&lt;em class="blog-post-photo-centered"&gt;Kongress on Plasma Mobile&lt;/em&gt;&lt;/p&gt;
&lt;h1&gt;Installation&lt;/h1&gt;
&lt;p&gt;Kongress is available in various mobile Linux distributions, like &lt;a href="https://kdebuild.manjaro.org{static}/images/"&gt;Manjaro&lt;/a&gt; and &lt;a href="https://postmarketos.org/"&gt;postmartketOS&lt;/a&gt;. You can also install the flatpak version of Kongress from the software repository of your distribution or directly from &lt;a href="https://flathub.org/apps/details/org.kde.kongress"&gt;flathub&lt;/a&gt;. Android users can also try the nightly build from the &lt;a href="https://community.kde.org/Android/FDroid"&gt;F-Droid repository&lt;/a&gt; of KDE.&lt;/p&gt;
&lt;h1&gt;Source code&lt;/h1&gt;
&lt;p&gt;The source code and signatures can be downloaded from &lt;a href="https://download.kde.org/stable/kongress/1.0/"&gt;download.kde.org&lt;/a&gt;.&lt;/p&gt;
&lt;h1&gt;Feedback&lt;/h1&gt;
&lt;p&gt;Feel free to provide your feedback and follow the discussion about its development on the &lt;a href="https://matrix.to/#/#plasmamobile:matrix.org"&gt;matrix channel&lt;/a&gt; of &lt;a href="https://www.plasma-mobile.org/"&gt;Plasma mobile&lt;/a&gt; and the &lt;a href="https://invent.kde.org/utilities/kongress/-/issues"&gt;gitlab project page&lt;/a&gt; of Kongress.&lt;/p&gt;</content><category term="kde"></category></entry><entry><title>Calindori and online calendars</title><link href="https://dimitris.cc/posts/calindori-and-online-calendars.html" rel="alternate"></link><published>2020-12-30T16:35:00+02:00</published><updated>2020-12-30T16:35:00+02:00</updated><author><name>Dimitris</name></author><id>tag:dimitris.cc,2020-12-30:/posts/calindori-and-online-calendars.html</id><summary type="html">&lt;p&gt;Currently, Calindori works with calendar data provided by files that follow the &lt;a href="https://tools.ietf.org/html/rfc5545"&gt;iCalendar&lt;/a&gt; specification, without offering an out-of-the-box way to synchronize your calendars with external sources, e.g. with Nextcloud. However, this will change in the future. In specific, a &lt;a href="https://invent.kde.org/plasma-mobile/calindori/-/issues/5"&gt;plan&lt;/a&gt; for this feature has been devised. The first step …&lt;/p&gt;</summary><content type="html">&lt;p&gt;Currently, Calindori works with calendar data provided by files that follow the &lt;a href="https://tools.ietf.org/html/rfc5545"&gt;iCalendar&lt;/a&gt; specification, without offering an out-of-the-box way to synchronize your calendars with external sources, e.g. with Nextcloud. However, this will change in the future. In specific, a &lt;a href="https://invent.kde.org/plasma-mobile/calindori/-/issues/5"&gt;plan&lt;/a&gt; for this feature has been devised. The first step, a &lt;a href="https://invent.kde.org/frameworks/kcalendarcore/-/merge_requests/20"&gt;plugin interface&lt;/a&gt; that will enable Calindori to use calendar data from various data sources is already in progress.&lt;/p&gt;
&lt;p&gt;Although Calindori works on Linux mobile, desktop and even Android, it has been created as the calendar of &lt;a href="https://www.plasma-mobile.org"&gt;Plasma Mobile&lt;/a&gt;. From this point of view, as soon as a personal information management (PIM) system is available on Plasma Mobile, Calindori will make use of it. However, such a system has not been implemented yet. Various ideas have been discussed on the Plasma Mobile sprints and community meetings. Personally, I am in favor of a sustainable, KDE community driven solution that will work well with Plasma desktop as well as taking into account the particularities of the mobile world, e.g. low energy consumption, "deep sleep" support, etc.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Calindori desktop" class="blog-post-photo-centered" src="https://dimitris.cc/images/calindori-desktop-202012.png"&gt;
&lt;em class="blog-post-photo-centered"&gt;Calindori on desktop&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;That being said, let me describe here an online calendar synchronization approach that has worked well for me. My personal workflow consists of a personal Nextcloud server -where my calendar is hosted- and various devices that make use of the Nextcloud calendar. As I said in the beginning, Calindori uses iCalendar files for calendar data. Thus, I looked for a mechanism that uses iCalendar files and synchronizes them with Nextcloud. During my research for such a solution, I stumbled upon Vdirsyncer.&lt;/p&gt;
&lt;p&gt;As the &lt;a href="https://github.com/pimutils/vdirsyncer"&gt;home page&lt;/a&gt; of the project reads, Vdirsyncer is a "command-line tool for synchronizing calendars and address books between a variety of servers and the local file system". Let me now describe how I managed to configure it and make it work with Calindori.&lt;/p&gt;
&lt;p&gt;Certainly, the first necessary step is to install Vdirsyncer. Luckily, it is available in various Linux repositories. E.g, on Ubuntu you just have to install the &lt;em&gt;vdirsyncer&lt;/em&gt; package. Next, according to the project &lt;a href="https://vdirsyncer.pimutils.org/en/stable/tutorial.html"&gt;documentation&lt;/a&gt;, a configuration file should be created. So, I created this &lt;code&gt;config&lt;/code&gt; file in the &lt;code&gt;~/.config/vdirsyncer&lt;/code&gt; directory:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;[general]&lt;/span&gt;
&lt;span class="na"&gt;status_path&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;~/.local/share/vdirsyncer/status/&amp;quot;&lt;/span&gt;

&lt;span class="k"&gt;[pair nc_myusername_caldav]&lt;/span&gt;
&lt;span class="na"&gt;a&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;nc_myusername_caldav_local&amp;quot;&lt;/span&gt;
&lt;span class="na"&gt;b&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;nc_myusername_caldav_remote&amp;quot;&lt;/span&gt;
&lt;span class="na"&gt;collections&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;[&amp;quot;from a&amp;quot;, &amp;quot;from b&amp;quot;]&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;[&amp;quot;color&amp;quot;]&lt;/span&gt;
&lt;span class="na"&gt;conflict_resolution&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;b wins&amp;quot;&lt;/span&gt;

&lt;span class="k"&gt;[storage nc_myusername_caldav_local]&lt;/span&gt;
&lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;singlefile&amp;quot;&lt;/span&gt;
&lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;~/.local/share/vdirsyncer/caldav/myusername/%s.ics&amp;quot;&lt;/span&gt;

&lt;span class="k"&gt;[storage nc_myusername_caldav_remote]&lt;/span&gt;
&lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;caldav&amp;quot;&lt;/span&gt;
&lt;span class="na"&gt;url&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;https://mynextcloudhost.xyz/remote.php/dav/calendars/myusername/personal/&amp;quot;&lt;/span&gt;
&lt;span class="na"&gt;username&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;myusername&amp;quot;&lt;/span&gt;
&lt;span class="na"&gt;password.fetch&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;[&amp;quot;command&amp;quot;, &amp;quot;keyring&amp;quot;, &amp;quot;-b&amp;quot;, &amp;quot;keyring.backends.kwallet.DBusKeyring&amp;quot;, &amp;quot;get&amp;quot;, &amp;quot;Nextcloud&amp;quot;, &amp;quot;myusername:https://mynextcloudhost.xyz/:0&amp;quot;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;So, in the configuration file, I defined two storages: 
- &lt;code&gt;nc_myusername_caldav_local&lt;/code&gt;, the local iCalendar file that Calindori will "consume" and the 
- &lt;code&gt;nc_myusername_caldav_remote&lt;/code&gt;, the Nextcloud &lt;code&gt;personal&lt;/code&gt; calendar.&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;pair nc_myusername_caldav&lt;/code&gt; section makes Vdirsyncer synchronize the calendar storages bidirectionally. In case of a conflict -an event or task has changed on both sides since the last sync- I opted for the conflict to be resolved in favor of the remote storage. &lt;/p&gt;
&lt;p&gt;With regards to authentication to Nextcloud for user &lt;code&gt;myusername&lt;/code&gt;, &lt;code&gt;keyring&lt;/code&gt; has been used in order to access the kwallet subsystem via D-Bus.&lt;/p&gt;
&lt;p&gt;&lt;img alt="KDE Wallet" class="blog-post-photo-centered-medium" src="https://dimitris.cc/images/kwallet.png"&gt;
&lt;em class="blog-post-photo-centered"&gt;KDE Wallet Nextcloud entry&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;This approach works perfectly for me since various passwords that I daily use have been stored in the kwallet which is opened just after logging in to my user session. If this approach does not fit your needs, there are various &lt;a href="https://vdirsyncer.pimutils.org/en/stable/keyring.html"&gt;alternatives&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Then, after creating the &lt;code&gt;~/.local/share/vdirsyncer/caldav/myusername&lt;/code&gt; directory and running:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;vdirsyncer discover
vdirsyncer sync
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;an iCalendar file will be created and populated with your Nextcloud tasks and events.&lt;/p&gt;
&lt;p&gt;The next step is to synchronize automatically at certain intervals. So, if you
- download &lt;a href="https://raw.githubusercontent.com/pimutils/vdirsyncer/master/contrib/vdirsyncer.service"&gt;vdirsyncer.service&lt;/a&gt; and &lt;a href="https://raw.githubusercontent.com/pimutils/vdirsyncer/master/contrib/vdirsyncer.timer"&gt;vdirsyncer.timer&lt;/a&gt;
- put them into &lt;code&gt;~/.local/share/systemd/user&lt;/code&gt; 
- activate and run the timer&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;systemctl --user enable vdirsyncer.timer
systemctl --user start vdirsyncer.timer
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;the Nextcloud calendar will be synchronized every 15 minutes.&lt;/p&gt;
&lt;p&gt;Finally, we need to let Calindori know about the Nextcloud calendar. The process is straight forward: navigate to &lt;em&gt;Settings &amp;gt; External &amp;gt; Add&lt;/em&gt; and add the Vdirsyncer calendar file. From now on, the tasks and events that are created either on Calindori or Nextcloud side are going to be synchronized between each other.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Add external calendar" class="blog-post-photo-centered-medium" src="https://dimitris.cc/images/add-external.png"&gt;
&lt;em class="blog-post-photo-centered"&gt;Add an external calendar&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Finally, let me clarify that this approach is not the way that Calindori and Plasma Mobile are going to offer online synchronization of calendars in the future. Nevertheless, Vdirsyncer is a nice, simple utility that enable users to use Nextcloud calendars in Calindori at the moment. It has worked pretty well for me, and I think that the Linux-on-mobile community will find it as an interesting solution for calendar synchronization.&lt;/p&gt;</content><category term="kde"></category></entry><entry><title>Calindori 1.3 has been released</title><link href="https://dimitris.cc/posts/calindori-13-has-been-released.html" rel="alternate"></link><published>2020-11-21T16:50:00+02:00</published><updated>2020-11-21T16:50:00+02:00</updated><author><name>Dimitris</name></author><id>tag:dimitris.cc,2020-11-21:/posts/calindori-13-has-been-released.html</id><summary type="html">&lt;p&gt;Calindori 1.3, the calendar application for mobile and convergent desktop, is now available. In this release, user interface refinements, fixes and under-the-hood changes can be found.&lt;/p&gt;
&lt;h1&gt;New features and improvements&lt;/h1&gt;
&lt;p&gt;First, Calindori now makes use of the Solid wake-up features, when running on Plasma 5.20 or later. In …&lt;/p&gt;</summary><content type="html">&lt;p&gt;Calindori 1.3, the calendar application for mobile and convergent desktop, is now available. In this release, user interface refinements, fixes and under-the-hood changes can be found.&lt;/p&gt;
&lt;h1&gt;New features and improvements&lt;/h1&gt;
&lt;p&gt;First, Calindori now makes use of the Solid wake-up features, when running on Plasma 5.20 or later. In specific, since version 5.20, Plasma has been offering a way that enables applications to inform Power Devil to wake them up at a time specified in the future. When this feature is used, applications facilitate the system to save power, by letting the system to be kept in deep sleep.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Calindori on Desktop" class="blog-post-photo-centered" src="https://dimitris.cc/images/calindori-desktop.png"&gt;
&lt;em class="blog-post-photo-centered"&gt;Calindori on desktop&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;In addition, in Calindori 1.3, several rough edges have been smoothed out as well as user interface features have been added. For example, in “All Tasks”, you can opt to display or hide the completed tasks, while on desktop, when a high resolution monitor is available, the month page is properly scaled. Moreover, when executing the application on a wide screen, the size of the global drawer has been reduced. This way we let the user focus on the main (calendar) page, without being distracted by the size of the side panel. Furthermore, the positioning of the confirmation dialog prompted when users try to delete an incidence has been fixed. Repeating and multi-day events are now properly displayed in the day and week views.&lt;/p&gt;
&lt;p&gt;&lt;img alt="All tasks" class="blog-post-photo-centered-medium" src="https://dimitris.cc/images/all-tasks.png"&gt;
&lt;em class="blog-post-photo-centered"&gt;All tasks on mobile&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Finally, support for external calendars has been added. In specific, external calendar files can be "attached" to Calindori and, whenever a file is modified by an external app, that change is shown in Calindori. So, users can now use a calendar that has been created by a different application. For example, if you have set-up calendar file synchronization with &lt;a href="http://vdirsyncer.pimutils.org/en/stable/"&gt;vdirsyncer&lt;/a&gt;, navigate to Calendar Management &amp;gt; External and add a calendar that’s also used by other devices.&lt;/p&gt;
&lt;h1&gt;Installation&lt;/h1&gt;
&lt;p&gt;Calindori is available on &lt;a href="https://neon.kde.org/"&gt;KDE Neon&lt;/a&gt; for &lt;a href="https://www.plasma-mobile.org/"&gt;Plasma Mobile&lt;/a&gt;, &lt;a href="https://kdebuild.manjaro.org{static}/images/"&gt;Manjaro&lt;/a&gt; and &lt;a href="https://postmarketos.org/"&gt;postmartketOS&lt;/a&gt;. You can also test the latest build of Calindori by using the &lt;a href="https://community.kde.org/Guidelines_and_HOWTOs/Flatpak"&gt;flaptpak nightlies&lt;/a&gt; kdeapps repository for ARM and x84_64. Users can also build it from source on any Linux distribution. The source code and signatures can be downloaded from &lt;a href="https://download.kde.org/stable/calindori/"&gt;download.kde.org&lt;/a&gt;.&lt;/p&gt;
&lt;h1&gt;Feedback&lt;/h1&gt;
&lt;p&gt;Feel free to provide your feedback and follow the discussion about its development on the &lt;a href="https://matrix.to/#/#plasmamobile:matrix.org"&gt;matrix channel&lt;/a&gt; of Plasma Mobile. Bugs can be reported in the &lt;a href="https://bugs.kde.org/buglist.cgi?quicksearch=calindori"&gt;bug tracker&lt;/a&gt; of KDE while design and development feedback can be added to gitlab &lt;a href="https://invent.kde.org/plasma-mobile/calindori/-/issues"&gt;issues&lt;/a&gt;.&lt;/p&gt;</content><category term="kde"></category></entry><entry><title>Calindori 1.2; the official one</title><link href="https://dimitris.cc/posts/calindori-12-the-official-one.html" rel="alternate"></link><published>2020-09-12T01:00:00+03:00</published><updated>2020-09-12T01:00:00+03:00</updated><author><name>Dimitris</name></author><id>tag:dimitris.cc,2020-09-12:/posts/calindori-12-the-official-one.html</id><summary type="html">&lt;p&gt;Calindori 1.2 is out! Although a couple of versions have also been tagged, that's the first stable release of Calindori as a KDE application.&lt;/p&gt;
&lt;p&gt;In this release, new ways to manage your schedule have been added, several rough edges have been smoothed out, and a set of mobile-desktop convergence …&lt;/p&gt;</summary><content type="html">&lt;p&gt;Calindori 1.2 is out! Although a couple of versions have also been tagged, that's the first stable release of Calindori as a KDE application.&lt;/p&gt;
&lt;p&gt;In this release, new ways to manage your schedule have been added, several rough edges have been smoothed out, and a set of mobile-desktop convergence bits have been introduced.&lt;/p&gt;
&lt;h1&gt;User interface&lt;/h1&gt;
&lt;p&gt;Now you can review the incidences -events or tasks- of each day in a day view. Incidences are presented in an hour list, ordered by their scheduled start date or end date. &lt;/p&gt;
&lt;p&gt;A week view has also been added, offering a per-week dashboard experience. On that dashboard, a list view displays the days of each week alongside with the incidences scheduled on each week day.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Week view" class="blog-post-photo-centered-medium" src="https://dimitris.cc/images/weekview_mobile.png"&gt;
&lt;em class="blog-post-photo-centered"&gt;Week view on mobile&lt;/em&gt;&lt;/p&gt;
&lt;h1&gt;Improvements&lt;/h1&gt;
&lt;p&gt;In Calindori 1.2, you can set the start and due date of a task as well as  schedule several alarms based on its start date. With regards to events, when a new one is created, a reminder is added by default and the start time is set to the next hour. But, Calindori wouldn't have been a KDE application, if you couldn't opt out or set a custom alarm time in the settings page.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Task editor" class="blog-post-photo-centered-medium" src="https://dimitris.cc/images/task_editor.png"&gt;
&lt;em class="blog-post-photo-centered"&gt;Task editor on mobile&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Several issues have also been addressed. For example, when an event or task starts and ends within the same hour, we ensure that it is always displayed. Moreover, the calendar import functionality has been modified in order to function properly on desktop.&lt;/p&gt;
&lt;p&gt;Finally, the vertical date swipes mechanism has been substituted with a simpler approach that improves the overall application performance.&lt;/p&gt;
&lt;h1&gt;Convergence&lt;/h1&gt;
&lt;p&gt;Apart from the convergence features that come with &lt;a href="https://develop.kde.org/frameworks/kirigami/"&gt;Kirigami&lt;/a&gt;, a set of Calindori specific ones have also been introduced. For example, when executing the application on desktop, the global drawer is always displayed at the left side of the screen, like a panel, while on mobile, it works like a modal drawer - the Plasma Discover way. &lt;/p&gt;
&lt;p&gt;In addition, when running Calindori on a device with a wide screen, the incidences of each week or day are displayed in a row, making use of the space available; on mobile devices, they are displayed in a column.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Day view" class="blog-post-photo-centered" src="https://dimitris.cc/images/dayview_desktop.png"&gt;
&lt;em class="blog-post-photo-centered"&gt;Day view on mobile&lt;/em&gt;&lt;/p&gt;
&lt;h1&gt;Installation&lt;/h1&gt;
&lt;p&gt;Calindori is available on &lt;a href="https://neon.kde.org/"&gt;KDE Neon &lt;/a&gt; for &lt;a href="https://www.plasma-mobile.org/"&gt;Plasma Mobile&lt;/a&gt; as well as on &lt;a href="https://postmarketos.org/"&gt;postmartketOS&lt;/a&gt;. It’s also available in the &lt;a href="https://community.kde.org/Guidelines_and_HOWTOs/Flatpak"&gt;flaptpak nightlies&lt;/a&gt; kdeapps repository for ARM and x84_64. Finally, you can also build it from source on your Linux desktop workstation. The source code and signatures can be downloaded from &lt;a href="https://download.kde.org/stable/calindori/"&gt;download.kde.org&lt;/a&gt;.&lt;/p&gt;
&lt;h1&gt;Feedback&lt;/h1&gt;
&lt;p&gt;Although you can use Calindori as your calendar application on any Linux desktop distribution, it is true that Calindori has been created for the Plasma Mobile ecosystem. So, feel free to provide your feedback and follow the  discussions about its development on the &lt;a href="https://matrix.to/#/#plasmamobile:matrix.org"&gt;matrix channel&lt;/a&gt; of Plasma Mobile.&lt;/p&gt;</content><category term="kde"></category></entry><entry><title>Going to Akademy - with a companion</title><link href="https://dimitris.cc/posts/going-to-akademy-with-a-companion.html" rel="alternate"></link><published>2020-08-30T14:00:00+03:00</published><updated>2020-08-30T14:00:00+03:00</updated><author><name>Dimitris</name></author><id>tag:dimitris.cc,2020-08-30:/posts/going-to-akademy-with-a-companion.html</id><summary type="html">&lt;p&gt;In January, one month before travelling to Brussels for FOSDEM 2020 (do you remember when the conferences were taking place in the non-virtual world?), I was following a conversation on Mastodon. The discussion was about free software companion apps that could be used on the PinePhone in FOSDEM. Calindori, the …&lt;/p&gt;</summary><content type="html">&lt;p&gt;In January, one month before travelling to Brussels for FOSDEM 2020 (do you remember when the conferences were taking place in the non-virtual world?), I was following a conversation on Mastodon. The discussion was about free software companion apps that could be used on the PinePhone in FOSDEM. Calindori, the calendar of &lt;a href="https://www.plasma-mobile.org/"&gt;Plasma Mobile&lt;/a&gt; was mentioned as a tool that could do the job.&lt;/p&gt;
&lt;p&gt;&lt;img alt="The Kongress icon" class="blog-post-photo-centered-medium" src="https://dimitris.cc/images/kongress.svg"&gt;
&lt;em class="blog-post-photo-centered-medium"&gt;The Kongress icon, thanks to Mathis Brüchert&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;The next day I found myself copying and modifying some Calindori code, adding new things and putting into the app the FOSDEM conference data. So, &lt;a href="https://invent.kde.org/utilities/kongress"&gt;Kongress&lt;/a&gt; was born; a companion application for congresses - virtual or not.&lt;/p&gt;
&lt;p&gt;With regard to the functionalities that it offers, Kongress includes:
- Checking the conference schedule
- Tagging talks as favorites
- Management of multiple conferences&lt;/p&gt;
&lt;p&gt;&lt;img alt="Conferences in Kongress" class="blog-post-photo-centered-medium" src="https://dimitris.cc/images/kongress_1.png"&gt;
&lt;em class="blog-post-photo-centered-medium"&gt;Conferences supported by Kongress&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;From a technical point of view, the user interface of Kongress is based on &lt;a href="https://api.kde.org/frameworks/kirigami/html/index.html"&gt;Kirigami&lt;/a&gt; and follows the &lt;a href="https://hig.kde.org/"&gt;KDE Human Interface Guidelines&lt;/a&gt; while the back-end system uses &lt;a href="https://api.kde.org/frameworks/kcalendarcore/html/index.html"&gt;KCalendarCore&lt;/a&gt;. For Kongress to be able to be used in a conference, the schedule should have been released in the &lt;a href="https://tools.ietf.org/html/rfc5545"&gt;ical&lt;/a&gt; format.&lt;/p&gt;
&lt;p&gt;Personally, I used it in FOSDEM 2020 and it fitted my needs. Since the annual conference of the KDE community, &lt;a href="https://akademy.kde.org/2020"&gt;Akademy 2020&lt;/a&gt;, has also been added to Kongress, I am going to use it in Akademy 2020 as well. I would be happy if Akademy attendees found it helpful and provided also their feedback.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Akademy 2020 schedule" class="blog-post-photo-centered-medium" src="https://dimitris.cc/images/kongress_2.png"&gt;
&lt;em class="blog-post-photo-centered-medium"&gt;Akademy 2020 schedule&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Kongress can be found in the unstable edition of &lt;a href="https://neon.kde.org/download"&gt;KDE Neon&lt;/a&gt; - so PinePhone users running KDE Neon can download it directly from the Neon repositories. It's also available in the &lt;a href="https://community.kde.org/Guidelines_and_HOWTOs/Flatpak"&gt;flaptpak nightlies&lt;/a&gt; kdeapps repository for ARM as well as x84_64 platforms. Finally, Android users can get it from the &lt;a href="https://community.kde.org/Android/FDroid"&gt;F-Droid repository&lt;/a&gt; of KDE. Nevertheless, since I usually do not spend my volunteer time on working on Android, that version may need some love; patches welcome.&lt;/p&gt;
&lt;p&gt;See you at Akademy 2020!&lt;/p&gt;</content><category term="kde"></category></entry><entry><title>Creating a serial adapter for PinePhone</title><link href="https://dimitris.cc/posts/creating-a-serial-adapter-for-pinephone.html" rel="alternate"></link><published>2020-05-01T12:00:00+03:00</published><updated>2020-05-01T12:00:00+03:00</updated><author><name>Dimitris</name></author><id>tag:dimitris.cc,2020-05-01:/posts/creating-a-serial-adapter-for-pinephone.html</id><summary type="html">&lt;p&gt;The other day I wanted to connect to my PinePhone from my desktop computer. Since USB tethering is not functional yet on KDE Neon that runs on the phone, a wifi connection should have been established on the phone side to get access to a terminal. But if you would …&lt;/p&gt;</summary><content type="html">&lt;p&gt;The other day I wanted to connect to my PinePhone from my desktop computer. Since USB tethering is not functional yet on KDE Neon that runs on the phone, a wifi connection should have been established on the phone side to get access to a terminal. But if you would like to debug the booting process or wifi is not an option, serial communication is the way to go.&lt;/p&gt;
&lt;p&gt;PinePhone offers serial connectivity through the headphones connector. In specific, the earphone jack works as expected -as an audio jack- if the 6th hardware switch on the back of the phone is ON. Switch it off, and the audio jack works as a serial adapter; a USB-to-jack serial console cable is also needed.&lt;/p&gt;
&lt;p&gt;Unfortunately, ordering such a cable during the covid-19 lock down would mean long delays. Luckily, after asking on the Plasma Mobile &lt;a href="https://webchat.kde.org/#/room/#plasmamobile:kde.org"&gt;matrix channel&lt;/a&gt;, Bhushan suggested I create my own converter cable, using an old USB to serial adapter -which I already had- and a spare audio cable. Let's see how to make it.&lt;/p&gt;
&lt;p&gt;&lt;img alt="USB to serial adapter " class="blog-post-photo-centered-medium" src="https://dimitris.cc/images/usb-serial-adapter.jpg"&gt;
&lt;em class="blog-post-photo-centered-medium"&gt;USB to serial adapter&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;At first, cut the audio cable. What I found after cutting was a copper wire ground sheath wrapped around two insulated audio signal wires. It is easy to bring out the wires by applying some fire.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Audio cable" class="blog-post-photo-centered-medium" src="https://dimitris.cc/images/uart-connection-1.jpg"&gt;
&lt;em class="blog-post-photo-centered"&gt;Audio cable wires&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Next, connect the audio cable wires to the USB serial cable endpoints. In my case, there are four connection points on the USB cable side:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Red +3.3 V&lt;/li&gt;
&lt;li&gt;Black GND&lt;/li&gt;
&lt;li&gt;White RXD&lt;/li&gt;
&lt;li&gt;Green TXD &lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;According to the information found at the &lt;a href="https://wiki.pine64.org/index.php/PinePhone#Serial_console"&gt;pine64 wiki&lt;/a&gt;, the cables should be connected like this:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Left audio to TXD&lt;/li&gt;
&lt;li&gt;Right audio to RXD&lt;/li&gt;
&lt;li&gt;Ground connection&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I also used some spare jump wires with solid tips to make the construction more stable, but that's not required. After insulating the connection with some tape, the serial cable is ready to use.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Ready to use cable" class="blog-post-photo-centered-medium" src="https://dimitris.cc/images/uart-connection-3.jpg"&gt;
&lt;em class="blog-post-photo-centered"&gt;Ready to use cable&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;So, it's time to test our construction. A friendly utility for serial connection is picocom. Enter:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;sudo picocom -b 115200 /dev/ttyUSB0 
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Then, switch on the PinePhone, and the booting process is shown on the terminal. It's also possible to login to an interactive shell. E.g., open another terminal and type:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;sudo bash -c &amp;quot;echo phablet &amp;gt; /dev/ttyUSB0&amp;quot;
sudo bash -c &amp;quot;echo 1234 &amp;gt; /dev/ttyUSB0&amp;quot;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;That's all. Happy PinePhone hacking!&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;This post was originally published in May 2020. On 1 Feb 2022 it has been updated: the voltage of the cable has been changed from 5 to 3.3.&lt;/p&gt;
&lt;/blockquote&gt;</content><category term="general"></category></entry><entry><title>Calindori 1.1 is out: reminders, repeating events and more</title><link href="https://dimitris.cc/posts/calindori-11-is-out-reminders-repeating-events-and-more.html" rel="alternate"></link><published>2020-02-19T13:00:00+02:00</published><updated>2020-02-19T13:00:00+02:00</updated><author><name>Dimitris</name></author><id>tag:dimitris.cc,2020-02-19:/posts/calindori-11-is-out-reminders-repeating-events-and-more.html</id><summary type="html">&lt;p&gt;A new version of Calindori, the calendar application of &lt;a href="https://www.plasma-mobile.org/"&gt;Plasma Mobile&lt;/a&gt;, is now available. In Calindori 1.1, a set of new features has been added as well as the user interface has been improved, following the KDE human interface &lt;a href="https://hig.kde.org/"&gt;guidelines&lt;/a&gt;.&lt;/p&gt;
&lt;h1&gt;Event reminders&lt;/h1&gt;
&lt;p&gt;You can now add reminders to calendar …&lt;/p&gt;</summary><content type="html">&lt;p&gt;A new version of Calindori, the calendar application of &lt;a href="https://www.plasma-mobile.org/"&gt;Plasma Mobile&lt;/a&gt;, is now available. In Calindori 1.1, a set of new features has been added as well as the user interface has been improved, following the KDE human interface &lt;a href="https://hig.kde.org/"&gt;guidelines&lt;/a&gt;.&lt;/p&gt;
&lt;h1&gt;Event reminders&lt;/h1&gt;
&lt;p&gt;You can now add reminders to calendar events. To manage event reminders, a separate background application, calindac, has been created. Calindac looks for alarms into the Calindori ical files and triggers alarm notifications. The users may dismiss the alarm displayed or suspend it for a configurable amount of time.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Add reminder" class="blog-post-photo-centered-medium" src="https://dimitris.cc/images/reminders.png"&gt;
&lt;em class="blog-post-photo-centered"&gt;Add reminder&lt;/em&gt;&lt;/p&gt;
&lt;h1&gt;Repeating events&lt;/h1&gt;
&lt;p&gt;In Calindori 1.1, repeating events can be added. In particular, you can mark an event as repeating on the event editor. An overlay sheet enables you to provide the repeating event details. Then, on the events list view, you can review the repeating information of each event.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Add repeating event" class="blog-post-photo-centered-medium" src="https://dimitris.cc/images/repeating.png"&gt;
&lt;em class="blog-post-photo-centered"&gt;Add repeating event&lt;/em&gt;&lt;/p&gt;
&lt;h1&gt;Month view swipes&lt;/h1&gt;
&lt;p&gt;To facilitate navigation among months, the month shown can be switched via vertical gestures; but you can still change the active month using the buttons on the bottom of the page.&lt;/p&gt;
&lt;h1&gt;Flat events view&lt;/h1&gt;
&lt;p&gt;To get an overview of your events, a flat view of all the registered events has been added. The events have been sorted by start date, displaying the upcoming events on the top of the page.&lt;/p&gt;
&lt;p&gt;&lt;img alt="All events page" class="blog-post-photo-centered-medium" src="https://dimitris.cc/images/flat_event.png"&gt;
&lt;em class="blog-post-photo-centered"&gt;All events page&lt;/em&gt;&lt;/p&gt;
&lt;h1&gt;User interface improvements&lt;/h1&gt;
&lt;p&gt;The user interface of the event and todo cards has been modified, to make the information displayed more clear. In specific, helper icons have been added as well as page space, which is important on mobile devices, has been saved. Finally, Mathis Brüchert designed a new Calindori icon. Thanks Mathis!&lt;/p&gt;
&lt;h1&gt;Under the hood&lt;/h1&gt;
&lt;p&gt;Last but not least, the Calindori code has been streamlined, improving performance and maintenace. Nicolas Fella put a lot of effort into this task.&lt;/p&gt;
&lt;h1&gt;Installing Calindori&lt;/h1&gt;
&lt;p&gt;Calindori is available on KDE Neon for Plasma Mobile as well as on &lt;a href="https://postmarketos.org/"&gt;postmartketOS&lt;/a&gt;. You can also install the &lt;a href="https://invent.kde.org/kde/calindori/-/releases"&gt;flatpak build&lt;/a&gt; on any Linux distribution. Finally, the nightly build of Calindori for Android can be found in the &lt;a href="https://community.kde.org/Android/FDroid"&gt;F-Droid instance&lt;/a&gt; of KDE.&lt;/p&gt;
&lt;h1&gt;Coming soon&lt;/h1&gt;
&lt;p&gt;Looking to the future, the most important piece to be added to Calindori is online calendar synchronization. The good news is that there is already work in progress on this. &lt;/p&gt;
&lt;p&gt;Meanwhile, if you are interested in getting involved in Plasma Mobile, you can check the &lt;a href="https://www.plasma-mobile.org/findyourway"&gt;Find your way&lt;/a&gt; guide and join us on &lt;a href="https://matrix.to/#/#plasmamobile:matrix.org"&gt;matrix&lt;/a&gt;.&lt;/p&gt;</content><category term="kde"></category></entry><entry><title>Creating a Kirigami application, the easy way</title><link href="https://dimitris.cc/posts/creating-a-kirigami-application-the-easy-way.html" rel="alternate"></link><published>2019-11-11T11:00:00+02:00</published><updated>2019-11-11T11:00:00+02:00</updated><author><name>Dimitris</name></author><id>tag:dimitris.cc,2019-11-11:/posts/creating-a-kirigami-application-the-easy-way.html</id><summary type="html">&lt;p&gt;Interested in getting started with Kirigami development in a few minutes? Since version 5.63 of the &lt;a href="https://kde.org/products/kirigami/"&gt;Kirigami framework&lt;/a&gt;, there is an easy way to do so: an application template. The template facilitates the creation of a new application, using CMake as the build system, and linking to Kirigami dynamically …&lt;/p&gt;</summary><content type="html">&lt;p&gt;Interested in getting started with Kirigami development in a few minutes? Since version 5.63 of the &lt;a href="https://kde.org/products/kirigami/"&gt;Kirigami framework&lt;/a&gt;, there is an easy way to do so: an application template. The template facilitates the creation of a new application, using CMake as the build system, and linking to Kirigami dynamically as a plugin at runtime.&lt;/p&gt;
&lt;p&gt;Let's see how it works. As a starting point, we will use a virtual environment running the latest &lt;a href="https://neon.kde.org/download"&gt;KDE Neon User edition&lt;/a&gt;. This is by no means a requirement, any Linux distribution with Kirigami 5.63 or later perfectly fits our needs. KDE Neon has been chosen just because it provides -by design- the latest Qt, KDE frameworks and applications. Moreover, a virtual machine will let us play fearlessly with the system directories.&lt;/p&gt;
&lt;p&gt;At first, we install the packages required:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;sudo apt install qtbase5-dev build-essential git gcc g++ qtdeclarative5-dev qml-module-qtquick-controls libqt5svg5-dev qtmultimedia5-dev automake cmake qtquickcontrols2-5-dev libkf5config-dev libkf5service-dev libkf5notifications-dev libkf5kiocore5 libkf5kio-dev qml-module-qtwebengine gettext extra-cmake-modules libkf5wallet-dev qtbase5-private-dev qtwebengine5-dev libkf5wallet-dev qt5-default kirigami2-dev kdevelop kapptemplate
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;We are ready to convert the template to a working application. We will do it in two different ways:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Using the &lt;em&gt;KAppTemplate&lt;/em&gt; tool&lt;/li&gt;
&lt;li&gt;With &lt;em&gt;KDevelop&lt;/em&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;The KAppTemplate way&lt;/h1&gt;
&lt;p&gt;KAppTemplate, the Qt and KDE template generator, consumes templates and generates source code. After launching &lt;em&gt;KAppTemplate&lt;/em&gt;, on the second page of the wizard, we select &lt;em&gt;Kirigami Application&lt;/em&gt;. The Kirigami template can be found under &lt;em&gt;Qt &amp;gt; Graphical&lt;/em&gt; on the leftmost panel. We also provide a project name, e.g. hellokirigami.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Choose your project" class="blog-post-photo-centered-medium" src="https://dimitris.cc/images/kapptemplate-project-template.png"&gt;
&lt;em class="blog-post-photo-centered"&gt;Choose your project template&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;On the last page of the wizard, we set the application directory and, optionally, some details about our application. &lt;/p&gt;
&lt;p&gt;&lt;img alt="Project properties" class="blog-post-photo-centered-medium" src="https://dimitris.cc/images/kapptemplate-project-properties.png"&gt;
&lt;em class="blog-post-photo-centered"&gt;Set the project properties&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;After clicking to &lt;em&gt;Generate&lt;/em&gt;, the source code of our application is created; we are ready to start building it.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;cd /home/user/src/hellokirigami
mkdir build
cd build
cmake  ..
make -j4
sudo make install
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Since the development environment is a virtual one, we are free to install our application in the system directories. Otherwise, in case of working on the host machine, custom install prefixes would be recommended.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Hello Kirigami" class="blog-post-photo-centered-medium" src="https://dimitris.cc/images/hello-kirigami-desktop.png"&gt;
&lt;em class="blog-post-photo-centered"&gt;Hello Kirigami&lt;/em&gt;&lt;/p&gt;
&lt;h1&gt;Using KDevelop&lt;/h1&gt;
&lt;p&gt;KDevelop is a full-featured IDE that integrates perfectly with application templates. On the &lt;em&gt;Project&lt;/em&gt; menu, after clicking to the &lt;em&gt;New From Template&lt;/em&gt; menu item, the application template wizard is shown. &lt;/p&gt;
&lt;p&gt;&lt;img alt="New from template" class="blog-post-photo-centered" src="https://dimitris.cc/images/kdevelop-new-from-template.png"&gt;
&lt;em class="blog-post-photo-centered"&gt;Create a KDevelop project from template&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;We just select the &lt;em&gt;Qt&lt;/em&gt; category on the leftmost panel and &lt;em&gt;Kirigami Application&lt;/em&gt; on the right one, and set a project name, e.g. &lt;em&gt;hellokirigami2&lt;/em&gt;. &lt;/p&gt;
&lt;p&gt;&lt;img alt="Kirigami template" class="blog-post-photo-centered-medium" src="https://dimitris.cc/images/kdevelop-kirigami-template.png"&gt;
&lt;em class="blog-post-photo-centered"&gt;Kirigami template on KDevelop&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;On the last screen of the template wizard, we may also select a version control system, if needed. Not this time.&lt;/p&gt;
&lt;p&gt;Back to KDevelop, we may set a custom installation prefix. Since we are not going to to install anything, we just select &lt;em&gt;Debug&lt;/em&gt; as &lt;em&gt;Build type&lt;/em&gt; and don't bother with installation prefixes.&lt;/p&gt;
&lt;p&gt;&lt;img alt="KDevelop build dir" class="blog-post-photo-centered-medium" src="https://dimitris.cc/images/kdevelop-build-dir.png"&gt;
&lt;em class="blog-post-photo-centered"&gt;Set the build directory&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;The &lt;em&gt;Projects&lt;/em&gt; pane enables us to examine the source code file hierarchy, while on the right we can see the code of each file. To test our application, we &lt;em&gt;Build&lt;/em&gt; and, finally, &lt;em&gt;Execute&lt;/em&gt; our application. Both actions can be found on the main toolbar.&lt;/p&gt;
&lt;p&gt;&lt;img alt="KDevelop build exec" class="blog-post-photo-centered" src="https://dimitris.cc/images/kdevelop-build-exec.png"&gt;
&lt;em class="blog-post-photo-centered"&gt;Build and execute&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Having clicked to &lt;em&gt;Execute&lt;/em&gt;, on the &lt;em&gt;Launch Configurations&lt;/em&gt; dialog, we &lt;em&gt;Add&lt;/em&gt; a new configuration, selecting the &lt;em&gt;scr/hellokirigami2&lt;/em&gt; project target. &lt;/p&gt;
&lt;p&gt;&lt;img alt="Configure launch" class="blog-post-photo-centered" src="https://dimitris.cc/images/kdevelop-set-target.png"&gt;
&lt;em class="blog-post-photo-centered"&gt;Set application target on KDevelop&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;To check how the application will run on &lt;a href="https://www.plasma-mobile.org/"&gt;Plasma Mobile&lt;/a&gt;, we have to configure again a little bit our environment. In particular, on the &lt;em&gt;Run&lt;/em&gt; menu, we click to the &lt;em&gt;Configure Launches&lt;/em&gt; item. The configuration dialog will be displayed.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Configure environment" class="blog-post-photo-centered" src="https://dimitris.cc/images/kdevelop-configure-env.png"&gt;
&lt;em class="blog-post-photo-centered"&gt;Launch configurations on KDevelop&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Then, we add the following to &lt;em&gt;Environment&lt;/em&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;QT_QUICK_CONTROLS_MOBILE=true&lt;/li&gt;
&lt;li&gt;QT_QUICK_CONTROLS_STYLE=Plasma&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img alt="Set environment variables" class="blog-post-photo-centered" src="https://dimitris.cc/images/kdevelop-configure-env-vars.png"&gt;
&lt;em class="blog-post-photo-centered"&gt;Set mobile environment variables&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;We are prompted with the mobile version of our application.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Hello Kirigami" class="blog-post-photo-centered-medium" src="https://dimitris.cc/images/hello-kirigami-mobile.png"&gt;
&lt;em class="blog-post-photo-centered"&gt;Hello Kirigami&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;That's all. It's time to add your own code bits. Happy hacking!&lt;/p&gt;</content><category term="kde"></category></entry><entry><title>Akademy, the pulse of a vibrant community</title><link href="https://dimitris.cc/posts/akademy-the-pulse-of-a-vibrant-community.html" rel="alternate"></link><published>2019-09-03T08:00:00+03:00</published><updated>2019-09-03T08:00:00+03:00</updated><author><name>Dimitris</name></author><id>tag:dimitris.cc,2019-09-03:/posts/akademy-the-pulse-of-a-vibrant-community.html</id><summary type="html">&lt;p&gt;Have you ever wondered how KDE, a global community of volunteers, can successfully create and maintain such a large set of software projects? Projects that, among many others, include image and video editing applications, a powerful desktop environment and frameworks that make the work of developers easier. If you have …&lt;/p&gt;</summary><content type="html">&lt;p&gt;Have you ever wondered how KDE, a global community of volunteers, can successfully create and maintain such a large set of software projects? Projects that, among many others, include image and video editing applications, a powerful desktop environment and frameworks that make the work of developers easier. If you have not found the answer yet, I recommend you to participate in &lt;a href="https://akademy.kde.org/2019"&gt;Akademy&lt;/a&gt;, the annual conference of the KDE community, because the answer is not a technical one.&lt;/p&gt;
&lt;p&gt;The answer is the community spirit. Regardless of nationality, sexual orientation or age, everyone feels comfortable in KDE. If you participate in a BoF session, raise your hand and shoot a question, no matter your technical expertise, every single person will pay attention to what you will say. KDE is an inclusive community; be respectful and tolerate others and you will be more than welcome.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Akademy 2018, Vienna " class="blog-post-photo-centered" src="https://dimitris.cc/images/akademy2018-groupphoto.jpg"&gt;
&lt;em class="blog-post-photo-centered"&gt;Akademy 2018, Vienna. Photo by Ingride Costa &lt;a href="https://creativecommons.org/licenses/by/4.0/"&gt;&lt;img alt="CC-BY" src="https://dimitris.cc/images/ccby-wee.png"&gt;&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;This year Akademy will take place in Milan, Italy, from the 7th to the 13th of September.  If you have the opportunity to be in Milan, don’t miss it. During the first two days, a set of very interesting talks will be given; then, BoFs, meetings and workshops will follow, where you will be able to learn new things and discuss the plans of the community for the years to come. Last but not the least, you will be among the first to know the results of the voting for the goals of the community for the next 2 to 3 years.&lt;/p&gt;
&lt;p&gt;During the conference, apart from attending the talks, I am planning to meet my friends from &lt;a href="https://www.plasma-mobile.org/"&gt;Plasma Mobile&lt;/a&gt; and discuss with them the current status as well as the next steps of the mobile platform of KDE. Moreover, on Thursday morning, Camilo -developer of Maui, &lt;a href="https://vvave.kde.org/"&gt;Vvave&lt;/a&gt; and many other interesting projects- and me, will host a 4-hour &lt;a href="https://community.kde.org/Akademy/2019/Thursday"&gt;workshop&lt;/a&gt;. &lt;/p&gt;
&lt;p&gt;Workshop participants will be introduced to application development with QML, &lt;a href="https://doc.qt.io/qt-5/qtquick-controls2-qmlmodule.html"&gt;Qt Quick Controls&lt;/a&gt; and &lt;a href="https://api.kde.org/frameworks/kirigami/html/index.html"&gt;Kirigami&lt;/a&gt;. Target audience is developers without previous experience  in QML; we will learn together how to set up the development environment and we will get familiar with the Qt Quick Controls module. Finally, we will find out what the Kirigami toolkit offers us, and we will hack a real Kirigami application. The only requirement is a workstation with the latest &lt;a href="https://neon.kde.org/download"&gt;KDE Neon user edition&lt;/a&gt;, either on host or a virtual machine. I should mention that KDE Neon is not an "official" suggestion by KDE; it has been chosen because it is familiar to us, so we will quickly and easily offer a working development environment. The workshop will start at 9.30 in the morning and Akademy attendees who are willing to get their hands dirty with QML are welcome.&lt;/p&gt;
&lt;p&gt;I wish everything to go as planned and to enjoy another Akademy; it is a reviving experience that gives you strength to keep on contributing. It would also be very nice to find some time and get to know the city; the &lt;a href="https://community.kde.org/Akademy/2019/A_brief_guide_to_Milan%27s_Gastronomy"&gt;gastronomy guide&lt;/a&gt; created by Riccardo looks very promising!&lt;/p&gt;
&lt;p&gt;&lt;img alt="Going to Akademy" src="https://dimitris.cc/images/Akademy2019BannerDuomoMilan.png"&gt;&lt;/p&gt;</content><category term="kde"></category></entry><entry><title>Organizing time on Plasma Mobile</title><link href="https://dimitris.cc/posts/organizing-time-on-plasma-mobile.html" rel="alternate"></link><published>2019-05-31T09:00:00+03:00</published><updated>2019-05-31T09:00:00+03:00</updated><author><name>Dimitris</name></author><id>tag:dimitris.cc,2019-05-31:/posts/organizing-time-on-plasma-mobile.html</id><summary type="html">&lt;p&gt;About a year ago the &lt;a href="https://phabricator.kde.org/project/view/28/"&gt;phabricator&lt;/a&gt; tasks of Plasma Mobile were extensively revamped. We tried to make clear the objective of each task, providing helpful resources and facilitating onboarding. Looking at the features needed to reach the “Plasma Mobile 1.0” milestone, the calendar application was sticking out. So, Calindori …&lt;/p&gt;</summary><content type="html">&lt;p&gt;About a year ago the &lt;a href="https://phabricator.kde.org/project/view/28/"&gt;phabricator&lt;/a&gt; tasks of Plasma Mobile were extensively revamped. We tried to make clear the objective of each task, providing helpful resources and facilitating onboarding. Looking at the features needed to reach the “Plasma Mobile 1.0” milestone, the calendar application was sticking out. So, Calindori was born (even though this name was coined some months later).&lt;/p&gt;
&lt;p&gt;Build on top of Qt Quick and Kirigami and following -or trying to follow- the KDE human interface &lt;a href="https://hig.kde.org/"&gt;guidelines&lt;/a&gt;, the whole point of Calindori is to help users manage their time. Through a clean user interface, it aims to offer the users an intuitive way to accomplish their tasks.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Calindori home and events pages" class="blog-post-photo-centered" src="https://dimitris.cc/images/home_n_events.png"&gt;
&lt;em class="blog-post-photo-centered"&gt;Calindori home and events pages&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;For the time being, Calindori provides the basic calendar functionalities: you can check previous and future dates and manage events and todos. Tasks and events of different domains (e.g. personal, business) can be included in different calendars, since multiple calendars are supported. Import functionality has also been added so as to make the transition of new users easier.&lt;/p&gt;
&lt;p&gt;You may test Calindori at the moment, either building the 1.0 &lt;a href="https://invent.kde.org/dkardarakos/calindori/releases"&gt;release&lt;/a&gt; from source or just installing the flatpak bundle. A Plasma Mobile phone is not a requirement for testing Calindori; it perfectly runs on Plasma or any other Linux desktop environment. It has been designed having in mind the needs of the mobile users, but the great advantage of using a framework like Kirigami is the adaptation of the user interface to desktop environments, with little or without additional development effort. It will also be very helpful to report issues and provide feedback on the gitlab &lt;a href="https://invent.kde.org/dkardarakos/calindori/issues"&gt;repository&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Behind the scenes, the &lt;a href="https://tools.ietf.org/html/rfc5545"&gt;iCalendar&lt;/a&gt; standard is followed, as implemented by the KDE KCalcore library. KDE/Qt Developers may also find interesting the date and time pickers included in the application. These components may be reviewed, enhanced and, why not, find their way to the KDE frameworks.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Calindori date and time pickers" class="blog-post-photo-centered" src="https://dimitris.cc/images/date_time_pickers.png"&gt;
&lt;em class="blog-post-photo-centered"&gt;Calindori date and time pickers&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Looking to the future, the support of repeating events and reminders are the first tasks that should be handled. Then, caldav should also be supported, enabling the users to synchronize their online calendars (e.g. Nextcloud). Nevertheless, that’s a task that should be discussed with the rest of the Plasma Mobile team. The next KDE Akademy at &lt;a href="https://dot.kde.org/2019/04/24/announcing-akademy-2019-milan-italy-september-7th-13th"&gt;Milan&lt;/a&gt; in September will be a great opportunity.&lt;/p&gt;
&lt;p&gt;The &lt;a href="https://www.plasma-mobile.org/"&gt;Plasma Mobile&lt;/a&gt; team has made a significant progress during the last months, trying hard to offer a KDE Plasma experience to the mobile users. Even though it is not an 100% complete mobile platform, the missing parts are slowly being added to the software stack. But a lot of interesting tasks are still available, waiting for people willing to help the Plasma Mobile ecosystem grow.&lt;/p&gt;
&lt;p&gt;In the next months a set of devices -like PinePhone and Librem 5- are expected, enabling the execution of Linux distributions on real mobile hardware. This will change significantly the free software mobile environment; it will open the way for more privacy friendly devices that could put the users in the driver’s seat, without spying on them or treating them like products. I believe it is the perfect time to &lt;a href="https://www.plasma-mobile.org/findyourway/"&gt;get involved&lt;/a&gt; with projects like Plasma Mobile, helping to create an open mobile platform that will bring a KDE breeze to mobile phones. &lt;/p&gt;</content><category term="kde"></category></entry><entry><title>Notes on the Plasma Mobile sprint</title><link href="https://dimitris.cc/posts/notes-on-the-plasma-mobile-sprint.html" rel="alternate"></link><published>2019-02-26T18:00:00+02:00</published><updated>2019-02-26T18:00:00+02:00</updated><author><name>Dimitris</name></author><id>tag:dimitris.cc,2019-02-26:/posts/notes-on-the-plasma-mobile-sprint.html</id><summary type="html">&lt;p&gt;As soon as I offered my mobile phone number for the proof of concept of sending an SMS from a phone running &lt;a href="https://www.plasma-mobile.org/"&gt;Plasma Mobile&lt;/a&gt;, I felt like a student in the Stanford Research Institute expecting the very &lt;a href="https://web.archive.org/web/20080308120314/http://www.engineer.ucla.edu/stories/2004/Internet35.htm"&gt;first message&lt;/a&gt; via the ARPANET from UCLA . OK, that’s certainly an exaggeration …&lt;/p&gt;</summary><content type="html">&lt;p&gt;As soon as I offered my mobile phone number for the proof of concept of sending an SMS from a phone running &lt;a href="https://www.plasma-mobile.org/"&gt;Plasma Mobile&lt;/a&gt;, I felt like a student in the Stanford Research Institute expecting the very &lt;a href="https://web.archive.org/web/20080308120314/http://www.engineer.ucla.edu/stories/2004/Internet35.htm"&gt;first message&lt;/a&gt; via the ARPANET from UCLA . OK, that’s certainly an exaggeration, but it shows that the sprint, except the technical achievements, it was also full of community spirit and sometimes, emotions. &lt;/p&gt;
&lt;p&gt;All these and many more happened in the Plasma Mobile sprint. From the 4th to the 9th of February the Plasma Mobile contributors met in the Endocode offices in Berlin. I was there for the whole week and I enjoyed every single moment.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Sending SMS with Plasma Mobile" class="blog-post-photo-centered" src="https://dimitris.cc/images/kde_sms.png"&gt;
&lt;em class="blog-post-photo-centered"&gt;Sending SMS with Plasma Mobile&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;But why to bother to get involved with Plasma Mobile? After all, every floss initiative on the mobile field is damned to fail, isn’t it? Even if this was true -which it is not, since every failure gives lessons and future initiatives may base their work on top of it- we really need mobile devices based on free software. &lt;/p&gt;
&lt;p&gt;Since the mobile phone already is the primary computational device -at least for content consumption and communications- we deserve devices that respect our digital rights. We need secure devices and platforms that respect our privacy - in reality, not like the funny popups of the websites, full of trackers, telling us that they value our privacy... And for this to happen, mobile users should take back the control of their devices. Relying on closed platforms, like the ones currently on the market, it is the phone that controls us; and this should not be the case. &lt;/p&gt;
&lt;p&gt;Why to bother to bring KDE Plasma to mobile phones? Well, the &lt;a href="https://kde.org/"&gt;KDE&lt;/a&gt; community has already shown that it can offer a full featured desktop workspace and a large ecosystem of applications. It is the desktop environment that I use on my professional and personal workstations and having contributed to KDE for many years I think that Plasma Mobile is the perfect place for me to be.&lt;/p&gt;
&lt;p&gt;The sprint was a quite successful event, from a technical as well as from a community viewpoint. We worked on several issues that we had set as priorities before the sprint;  high DPI scaling has improved, new features have been added and bugs have been fixed on applications like Angelfish, Kirigami Gallery and Discover (among others) and a new rootfs for devices running on Halium has been published. Also, we configured the telephony infrastructure and we revamped the documentation. &lt;/p&gt;
&lt;p&gt;&lt;img alt="The Plasma Mobile team" class="blog-post-photo-centered" src="https://dimitris.cc/images/plamo_team.jpg"&gt;
&lt;em class="blog-post-photo-centered"&gt;Part of the team - important pieces missing&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Most importantly, we strengthened the bonds within and outside the team. Working together, sharing ideas, talking about KDE, technology and many more for a whole week is invaluable for building the foundations of a successful team. At the same time, since we are not creating Plasma Mobile for ourselves or for a bunch of friends, we spent a couple of hours on the sprint topics suggested by the community. Also, the &lt;a href="https://www.reddit.com/r/linux/comments/anspo5/we_are_plasma_mobile_developers_ama/"&gt;AMA session&lt;/a&gt; was a great opportunity for us to get feedback from the community, reflect on what we have done so far, what it is the impact of our work, and what we think about the future. &lt;/p&gt;
&lt;p&gt;Looking to the future, it is not easy to make predictions about free software on mobile. On the one hand, there are many external factors -binary blobs, old and customized Linux kernels, the imminent nightmare of Google Fuchsia, to name some of them- that put obstacles on our way. On the other hand, there are communities like &lt;a href="https://halium.org/"&gt;Halium&lt;/a&gt; and &lt;a href="https://postmarketos.org/"&gt;postmarketOS&lt;/a&gt; that offer us the opportunity to run Plasma Mobile on various devices; and there is also hardware like PinePhone and Librem 5.&lt;/p&gt;
&lt;p&gt;What is certainly true is that the current proprietary mobile environment does not fit many of us. And it also true that working on a free software project for mobile devices is challenging, exciting and by all means it is worth it. Feel free to &lt;a href="https://www.plasma-mobile.org/join/"&gt;join&lt;/a&gt; us.&lt;/p&gt;</content><category term="kde"></category></entry></feed>