Bilal Elmoussaoui - My BlogPersonal blog & portfolioZola2023-11-30T00:00:00+00:00https://belmoussaoui.com/blog/atom.xmlReducing Mutter dependencies2023-11-30T00:00:00+00:002023-11-30T00:00:00+00:00
Unknown
https://belmoussaoui.com/blog/18-reducing-mutter-dependencies/<p>Mutter, if you don't know what it is, is a Wayland display server and an X11 window manager and compositor library. It used by <a rel="nofollow noreferrer" href="https://gitlab.gnome.org/GNOME/gnome-shell/">GNOME Shell</a>, <a rel="nofollow noreferrer" href="https://github.com/elementary/gala/">Gala</a>, <a rel="nofollow noreferrer" href="https://gitlab.gnome.org/GNOME/gnome-kiosk/">GNOME Kiosk</a> and possibly other old forks out there.</p>
<p>The project was born from merging <a rel="nofollow noreferrer" href="https://gitlab.gnome.org/Archive/clutter">Clutter</a>, <a rel="nofollow noreferrer" href="https://gitlab.gnome.org/Archive/cogl">Cogl</a> and <a rel="nofollow noreferrer" href="https://en.wikipedia.org/wiki/Metacity">Metacity</a> so it has/had a lot of cruft and technical debts carried over time.</p>
<p>In this blog post, I will go through the list of things I worked on the last few months to reduce that and my plans for the upcoming year.</p>
<h2 id="reducing-build-runtime-dependencies">Reducing build/runtime dependencies</h2>
<p>After going through the list of dependencies used by Mutter, in a Wayland-only use case, I found the following ones that could potentially be completely removed or made optional</p>
<h3 id="zenity">zenity</h3>
<p>A runtime dependency, it was used to display various "complex" X11-specific dialogues that can't be replicated easily using Clutter.</p>
<p>Those dialogs were no longer useful and the dependency got removed in <a rel="nofollow noreferrer" href="https://gitlab.gnome.org/GNOME/mutter/-/merge_requests/2370">!2370</a>. Note that Mutter still uses Zenity for x11-specific tests.</p>
<h3 id="libcanberra">libcanberra</h3>
<p>Used by Mutter to play sounds in some contexts and also provides a public API to do so, in your custom shell. For embedded use cases, the functionality is not useful, especially since libcanberra ends up pulling gstreamer.</p>
<p>With <a rel="nofollow noreferrer" href="https://gitlab.gnome.org/GNOME/mutter/-/merge_requests/2375">!2375</a>, the functionality can be disabled at build time with <code>-Dsound_player=false</code> making <a rel="nofollow noreferrer" href="https://gnome.pages.gitlab.gnome.org/mutter/meta/class.SoundPlayer.html"><code>MetaSoundPlayer</code></a> not produce any sound.</p>
<h3 id="gnome-desktop">gnome-desktop</h3>
<p>Used internally by Mutter to retrieve the monitor vendor name before falling back to "Undefined".</p>
<p>With <a rel="nofollow noreferrer" href="https://gitlab.gnome.org/GNOME/mutter/-/merge_requests/2317">!2317</a>, the functionality can be disabled with <code>-Dlibgnome_desktop=false</code></p>
<h3 id="gnome-settings-daemon">gnome-settings-daemon</h3>
<p>A runtime dependency that provides the <code>org.gnome.settings-daemon.peripherals.touchscreen orientation-lock</code> setting, which is used to disable automatically updating the display orientation.</p>
<p>With <a rel="nofollow noreferrer" href="https://gitlab.gnome.org/GNOME/mutter/-/merge_requests/2398">!2398</a>, Mutter checks if the setting scheme is available first making the runtime dependency optional.</p>
<h3 id="gtk">gtk</h3>
<p>Under X11, GTK is used for drawing the window decorations. Some parts of GTK APIs ended up being used elsewhere for simplicity at that time. Ideally, shortly, we would have the possibility to build Mutter without x11 support, keeping the GTK dependency in that case doesn't make much sense.</p>
<p>With <a rel="nofollow noreferrer" href="https://gitlab.gnome.org/GNOME/mutter/-/merge_requests/2407">!2407</a>, the usage of GTK inside Mutter was reduced to drawing the window clients decorations, which Carlos Garnacho took care of handling in <a rel="nofollow noreferrer" href="https://gitlab.gnome.org/GNOME/mutter/-/merge_requests/2175">!2175</a>.</p>
<h3 id="json-glib">json-glib</h3>
<p>Clutter, could create <a rel="nofollow noreferrer" href="https://gnome.pages.gitlab.gnome.org/mutter/clutter/class.Actor.html"><code>ClutterActor</code></a>s from a JSON file, but nothing is using that feature since the merge of Clutter inside Mutter. The library was also used to serialize <a rel="nofollow noreferrer" href="https://gnome.pages.gitlab.gnome.org/mutter/clutter/class.PaintNode.html"><code>ClutterPaintNode</code></a>s for debugging purposes.</p>
<p>With <a rel="nofollow noreferrer" href="https://gitlab.gnome.org/GNOME/mutter/-/merge_requests/3354">!3354</a>, both use cases were removed and the dependency was dropped.</p>
<h3 id="gdk-pixbuf">gdk-pixbuf</h3>
<p>The library was used to provide APIs for creating <a rel="nofollow noreferrer" href="https://gnome.pages.gitlab.gnome.org/mutter/cogl/class.Texture.html"><code>CoglTexture</code></a>s or <a rel="nofollow noreferrer" href="https://gnome.pages.gitlab.gnome.org/mutter/cogl/class.Bitmap.html"><code>CoglBitmap</code></a> from a file and internally with <a rel="nofollow noreferrer" href="https://gnome.pages.gitlab.gnome.org/mutter/meta/class.BackgroundImageCache.html"><code>MetaBackgroundImageCache</code></a>for caching the background textures.</p>
<p>With <a rel="nofollow noreferrer" href="https://gitlab.gnome.org/GNOME/mutter/-/merge_requests/3097">!3097</a>, we only have the <code>MetaBackgroundImageCache</code> use case which needs some refactoring.</p>
<h3 id="cairo">cairo</h3>
<p>When I looked at building Mutter without X11, I noticed that cairo ends up pulling some X11 bits through the cairo-xlib backend and wondered how hard it would be to completely get rid of cairo usage in the Wayland-only code path.</p>
<p>The first step in this adventure was to <a rel="nofollow noreferrer" href="https://gitlab.gnome.org/GNOME/mutter/-/merge_requests/3128">replace <code>cairo_rectangle_int_t</code></a>, especially since Mutter had a <code>MetaRectangle</code> type anyways...But that meant <a rel="nofollow noreferrer" href="https://gitlab.gnome.org/GNOME/mutter/-/merge_requests/3292">replacing <code>cairo_region_t</code> as well</a>, which thankfully is just a ref counted <code>pixman_region32_t</code> wrapper. With those two changes, the cairo usage dropped drastically but was still far from enough.</p>
<ul>
<li><a rel="nofollow noreferrer" href="https://gnome.pages.gitlab.gnome.org/mutter/meta/property.Window.icon.html"><code>MetaWindow:icon</code></a> / <a rel="nofollow noreferrer" href="https://gnome.pages.gitlab.gnome.org/mutter/meta/property.Window.mini-icon.html"><code>MetaWindow:mini-icon</code></a> uses a <code>cairo_surface_t</code> which probably can either be dropped or replaced with something like a <code>CoglTexture</code></li>
<li><a rel="nofollow noreferrer" href="https://gnome.pages.gitlab.gnome.org/mutter/clutter/class.Canvas.html"><code>ClutterCanvas</code></a> uses cairo for drawing. It is only used by <code>StDrawingArea</code>, a <code>ClutterActor</code> subclass provided in GNOME Shell. The idea is to merge both in GNOME Shell and drop that API from Mutter</li>
<li>pango / pangocairo integration, which opens the question of whether the whole font rendering bits should be moved to GNOME Shell and leave Mutter outside of that business or if it should be made optional</li>
<li>Other use cases requiring case-by-case handling</li>
</ul>
<h3 id="x11">X11</h3>
<p>Well, Wayland is no news and Xwayland support allows keeping backward compatibility with X11-only applications. The idea is to allow building Mutter with Wayland only with or without Xwayland.</p>
<p>I have opened <a rel="nofollow noreferrer" href="https://gitlab.gnome.org/GNOME/mutter/-/issues/2272">#2272</a> which keeps track of the progress as well as the remaining tasks if someone is interested in helping with that!</p>
<h2 id="replacing-removing-deprecated-apis">Replacing/Removing deprecated APIs</h2>
<p>Cogl/Clutter before being merged with Metacity contained various deprecated APIs that were intended to be removed in the next major release of those libraries, which never happened. With the help of Zander Brown and Florian Müllner, we have managed to significantly reduce those.</p>
<p>Cogl also contained a <code>CoglObject</code> base class that is not a <code>GObject</code> for historical reasons. I took it as a personal challenge to <a rel="nofollow noreferrer" href="https://gitlab.gnome.org/GNOME/mutter/-/merge_requests/3193">get rid of it</a></p>
<h2 id="improving-documentation-of-the-public-api">Improving documentation of the public API</h2>
<p>Mutter and its various libraries already had a build time option to generate docs using <a rel="nofollow noreferrer" href="https://gitlab.gnome.org/GNOME/gi-docgen">gi-docgen</a> but the CI wasn't building and publishing them somewhere on Gitlab Pages. I <a rel="nofollow noreferrer" href="https://gitlab.gnome.org/GNOME/mutter/-/merge_requests/2441">have</a> <a rel="nofollow noreferrer" href="https://gitlab.gnome.org/GNOME/mutter/-/merge_requests/2708">also</a> <a rel="nofollow noreferrer" href="https://gitlab.gnome.org/GNOME/mutter/-/merge_requests/2939">ported</a> portion of the documentation to use gi-docgen annotations instead of the old gtk-doc way.</p>
<p>There is still a lot of work to be done to improve the quality of the documentation but we are getting there.</p>
<p>The docs are nowadays available at <a rel="nofollow noreferrer" href="https://gitlab.gnome.org/GNOME/mutter#contributing">https://gitlab.gnome.org/GNOME/mutter#contributing</a>.</p>
<h2 id="next-year">Next year?</h2>
<p>When I look back at the amount of changes I have managed to contribute to Mutter the last few months, I don't believe it was that much, but at the same time remind myself there is still a lot to be done, as usual.</p>
<p>For next year, I plan to continue pushing forward the effort to build Mutter without x11 / cairo / gdk-pixbuf and various upstream involvement where possible.</p>
<p>Till then, stay safe</p>
The journey of an open source contributor2023-09-29T00:00:00+00:002023-09-29T00:00:00+00:00
Unknown
https://belmoussaoui.com/blog/17-the-journey-of-a-contributor/<h2 id="the-journey">The journey</h2>
<p>In early 2019, the <a rel="nofollow noreferrer" href="https://www.gnome.org/">GNOME project</a> migrated to GitLab which for someone unfamiliar at that time with submitting patches by email it was the perfect timing to get involved.</p>
<p>I started from the very bottom of the stack by <a href="../1-gnome-initiative-appdata/">contributing to the Appdata/Metainfo</a> files of various applications. If you are not familiar with such files, they are used to describe the application to the Application Store by providing a name, screenshots, various links and release notes.</p>
<h3 id="sound-recorder">Sound Recorder</h3>
<p>The sound recorder application built for the GNOME desktop at that time was unmaintained, it didn't follow the up to date paradigms of building a modern GTK application. So I proposed a <a rel="nofollow noreferrer" href="https://summerofcode.withgoogle.com/archive/2020/projects/5926277921374208">Google Summer of Code in 2020</a> to modernize the code base. The project ended up being a success from an engineering point of view but the social/community aspect of it wasn't what I expected.</p>
<h3 id="clocks">Clocks</h3>
<p>Few months after taking over Sound Recorder, I convinced Zander Brown to join effort and <a href="../9-tick-tock-clocks-got-redesigned">modernize Clocks</a>. This time, I was involved in updating two panels out of the fourth that Clocks has instead of mentoring someone to do it. Thanks to the extensive reviews I got from Zander, it helped me a lot learn a ton of new things.</p>
<h3 id="design-tooling">Design tooling</h3>
<p>Just before deciding to become the maintainer of yet another application. Few people in the GNOME community were trying to convince me about this fancy new programming language called Rust. So instead of contributing to an existing code base I decided to write a Rust application called <a rel="nofollow noreferrer" href="https://gitlab.gnome.org/World/design/contrast">Contrast</a> and then an other one called <a rel="nofollow noreferrer" href="https://gitlab.gnome.org/World/design/icon-library">Icon Library</a>. I hated Rust at that time or to be more precise I hated the incomplete GTK 3 Rust bindings. The lack of composite templates support, creating custom widgets in GTK 3 being difficult and the huge amount of boilerplate involved in the process so somehow I found myself contributing few things here and there to the gtk-rs bindings.</p>
<p>I ended up also writing <a rel="nofollow noreferrer" href="https://gitlab.gnome.org/World/design/symbolic-preview/">Symbolic Preview</a> and porting <a rel="nofollow noreferrer" href="https://gitlab.gnome.org/World/design/app-icon-preview/">App Icon Preview</a> from Vala to Rust.</p>
<h3 id="characters">Characters</h3>
<p>Somehow, I still didn't learn the lesson and decided with Christopher Davis this time to take over Characters. I did a huge clean up of the code base, which helped with porting it to GTK 4 later.</p>
<h3 id="bindings-bindings-everywhere">Bindings, bindings everywhere</h3>
<p>Few months before the first release of GTK 4, I was already familiar with the basics of how the Rust bindings of GObject based libraries are generated. We had a very rough, experimental and no longer working GTK 4 bindings at that time and I decided that if we are going to make Rust the go to language for writing your next pet GTK application, this has to be our chance.</p>
<p>So I rebased the bindings and made them work with the latest APIs changes upstream, contributed a ton of code to GIR. The unfortunately named like that tool to generate Rust bindings from a GIR file. I have also worked on fixing annotation in the APIs upstream and ended up taking more and more responsibilities in the Rust bindings ecosystem.</p>
<p>Of course, great bindings are nice but we also need various important libraries to make the GTK 4 Rust bindings an attractive option. So I wrote ASHPD for interacting with the portals, bootstrapped a GStreamer plugin so you can render your camera stream or a video into a GTK widget, made it much simpler to write a GNOME Shell Search provider and recently co-rewrote libsecret in Rust.</p>
<h2 id="new-adventures">New adventures</h2>
<p>Since I joined Red Hat last year, I have been writing C. Yes, as you read it C. And to be honest? I enjoy it but I hate it at the same time.</p>
<p>This opportunity allowed me to learn more which has been the purpose of this journey so far. It also allowed me to contribute various patches to Mutter and recently to QEMU and help Christian Hergert with <a rel="nofollow noreferrer" href="https://gitlab.gnome.org/GNOME/libmks/">libmks</a>.</p>
<p>So far the journey has been amazing and the learning never stops but at some point, one has to stop thinking that a single person can maintain 10+ applications, 50+ Rust crates, have a full time job and keep a decent social life.
If you notice a commit dropping myself from the maintainers list of a project; now you would know why.</p>
Damage areas across the VirtIO space2023-08-15T00:00:00+00:002023-08-15T00:00:00+00:00
Unknown
https://belmoussaoui.com/blog/16-damage-areas-across-the-virtio-space/<p>In the last few months, I have been trying to improve the default UI shipped by <a rel="nofollow noreferrer" href="https://www.qemu.org/">QEMU</a>. As you might not know, QEMU ships with various UI backends: GTK, SDL, Cocoa and recently a DBus one.</p>
<p>I first started trying to port the GTK 3 backend to GTK 4 but faced some issues where I couldn't use <code>GtkApplication</code> as it starts its own <code>GMainLoop</code> which interferes with some god knows what internal <code>GMainLoop</code> started by QEMU itself. My intention was not to only do a simple port but also to see how we could optimize the rendering path as well.</p>
<p>At that time, I also learned that Christian Hergert started working on <a rel="nofollow noreferrer" href="https://gitlab.gnome.org/GNOME/libmks">libmks</a>, a new client-side C library of the DBus backend as he has the intention of using it in GNOME Builder. Marc-André Lureau, one of the upstream QEMU maintainers, is also working on something similar, with a larger scope and using Rust called <a rel="nofollow noreferrer" href="https://gitlab.com/marcandre.lureau/qemu-display/">RDW</a>, a Remote Desktop Widget to rule them all.</p>
<h2 id="damage-areas">Damage areas</h2>
<p>From a rendering perspective, the major difference between libmks and RDW at that time is that libmks used a custom <code>GdkPaintable</code> doing a tiled rendering of a <code>GLTexture</code> imported from a <code>DMABuf</code> as GTK had no API to set the damaged region of a <code>GLTexture</code> and it was doing a pointer comparison for a <code>GSK_TEXTURE_NODE</code> render nodes, hence the usage of a tiled rendering. RDW on the other hand was using a plain <code>GtkGLArea</code>.</p>
<p>Christian Hergert also shared with Javier Martinez Canillas and me about his findings while working on libmks:</p>
<ul>
<li>
<p>The VirtIO GPU DRM driver wasn't enabling the frame buffer damage clips property causing a full scanout. This is now fixed thanks to Javier <a rel="nofollow noreferrer" href="https://www.spinics.net/lists/kernel/msg4715781.html">https://www.spinics.net/lists/kernel/msg4715781.html</a></p>
</li>
<li>
<p>QEMU wasn't propagating the damaged area properly which is also fixed thanks to Christian who wrote the initial patch & I slightly changed it to use Pixman instead of Cairo <a rel="nofollow noreferrer" href="https://patchew.org/QEMU/20230814125802.102160-1-belmouss@redhat.com/">https://patchew.org/QEMU/20230814125802.102160-1-belmouss@redhat.com/</a></p>
</li>
</ul>
<p>So now that we have fixed the VirtIO GPU driver and QEMU, we should be able to get proper damage reporting information from the guest, right??? Well, that is what Javier & I were hoping for. A few days into debugging what was going on without much luck, I met Robert Mader during Linux App Summit and discussed the issue with him and he mentioned a certain deny list of drivers from using the atomic KMS API in Mutter. Interesting.</p>
<p>The Monday after LAS, I shared the info with Javier and we found out the <a rel="nofollow noreferrer" href="https://gitlab.gnome.org/GNOME/mutter/-/merge_requests/2040">root cause</a> of our pain. As the atomic KMS API has no properties for setting the cursor hotspot, which is useful for the virtualized use case, it was put on a deny list until such properties are added to the kernel. Javier also found that Zack Rusin from VMware is already on it, he submitted a <a rel="nofollow noreferrer" href="https://lore.kernel.org/all/20220712033246.1148476-1-zack@kde.org/#r">patch for the kernel</a> and had a local branch for Mutter as well. Albert Esteve tested the Kernel/Mutter patches and confirmed they were good. He also wrote an <a rel="nofollow noreferrer" href="https://gitlab.freedesktop.org/drm/igt-gpu-tools">IGT</a> test, which will hopefully help get the patch merged soon.</p>
<p>Around that time, Benjamin Otte was working on adding a new builder-like API to simplify the creation of a <code>GdkGLTexture</code>, with a very neat feature, allowing to set the exact damage region instead of doing a pointer comparison. I offered to test the API and ported libmks to use it.</p>
<h2 id="other-improvements">Other improvements</h2>
<p>Sergio Lopez Pascual worked on adding multi-touch support to virtio-input/QEMU which I then exposed on the QEMU DBus interface & implemented the libmks side of it.</p>
<h2 id="for-the-future">For the future</h2>
<ul>
<li>We still don't have all the required features to make libmks a drop-in replacement for Spice usage in GNOME Boxes like <a rel="nofollow noreferrer" href="https://gitlab.gnome.org/GNOME/libmks/-/issues/10">USB redirection</a>, <a rel="nofollow noreferrer" href="https://gitlab.gnome.org/GNOME/libmks/-/issues/7">Clipboard sharing</a>. I have WIP branches for both, one day I will finish them.</li>
<li>Porting GNOME Boxes to GTK 4</li>
<li>The Kernel / Mutter patches from Zack being merged</li>
</ul>
<p>Once all these pieces land, you will hopefully be able to enjoy a slightly better experience when running a virtualized OS that properly propagates the damaged regions.</p>
<p>For now, you can try libmks either by using the debugging tool shipped with the library or by using this very small client I wrote on top of it, <a rel="nofollow noreferrer" href="https://gitlab.gnome.org/bilelmoussaoui/snowglobe">Snowglobe</a>.</p>
<h2 id="acknowledgement">Acknowledgement</h2>
<p>As you can tell, a bunch of components and people were involved in this few months trip. I would like to thank everyone for taking the time to explain basic things to me as well as helping out to get this done! You can now enjoy a well deserved screen recording showing the implication of these changes</p>
<video controls>
<source src="/posts/16-damage-areas-across-the-virtio-space/libmks.webm" type="video/webm" />
<p>Download the
<a href="/posts/16-damage-areas-across-the-virtio-space/libmks.webm">WEBM</a>
video.
</video></p>
Making Rust attractive for writing GTK applications2022-10-23T00:00:00+00:002022-10-23T00:00:00+00:00
Unknown
https://belmoussaoui.com/blog/15-making-rust-attractive-for-writing-gtk-applications/<p><a rel="nofollow noreferrer" href="https://www.rust-lang.org/">Rust</a>, the programming language, has been gaining traction across many software disciplines - support for it has landed in the <a href="https://belmoussaoui.com/blog/15-making-rust-attractive-for-writing-gtk-applications/(https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=8aebac82933ff1a7c8eede18cab11e1115e2062b)">upstream Linux kernel</a>, developers have been using it for games, websites, low-level OS components, and desktop applications.</p>
<p>The gtk-rs team has been doing an impressive amount of work during the last few years to make the experience of using GObject-based libraries in Rust enjoyable by providing high-quality, memory-safe bindings around those libraries, generated with <a rel="nofollow noreferrer" href="https://github.com/gtk-rs/gir">gir</a> from the <a rel="nofollow noreferrer" href="https://gi.readthedocs.io/en/latest/">introspection data</a>.</p>
<p>Approximately two years ago and a few months before the release of <a rel="nofollow noreferrer" href="https://gtk.org/">GTK</a> 4, I decided to take over the maintenance of <a rel="nofollow noreferrer" href="https://github.com/gtk-rs/gtk4-rs">gtk4-rs</a> and push forward the initial work made by Xiang Fan during a Google Summer of Code internship. Nowadays, these are the most used GTK 4 bindings out there with probably more than 100 applications written in it, ranging from simple applications like <a rel="nofollow noreferrer" href="https://flathub.org/apps/details/org.gnome.design.Contrast">Contrast</a> to complex ones like <a rel="nofollow noreferrer" href="https://github.com/melix99/telegrand">Telegrand</a> or <a rel="nofollow noreferrer" href="https://flathub.org/apps/details/dev.alextren.Spot">Spot</a>.</p>
<p>In this post, I will talk about the current status and what we have achieved since the first release of <code>gtk4-rs</code>.</p>
<h2 id="bindings">Bindings</h2>
<p>As mentioned above, a good portion of the bindings is generated automatically using <a rel="nofollow noreferrer" href="https://github.com/gtk-rs/gir">gir</a>. But sometimes, a manual implementation is needed, like in the following cases:</p>
<ul>
<li>Make the code more Rust idiomatic</li>
<li>Handling cases that are too specific to be supported by <a rel="nofollow noreferrer" href="https://github.com/gtk-rs/gir">gir</a>. e.g, a <code>x_get_type</code> function being exposed only in a specific version.</li>
<li>Writing the necessary infrastructure code to allow developers to create custom GObjects, e.g. custom widgets or GStreamer plugins</li>
</ul>
<p>Currently, gtk4-rs is composed of ~170 000 lines of code automatically generated and ~26 000 manually written, which is approximately 13% of manual code.</p>
<h3 id="subclassing">Subclassing</h3>
<p>In the early days of <code>gtk3-rs</code>, the infrastructure for writing custom subclassses wasn't fully there yet, especially the amount of manual code that had to be written for supporting all the virtual functions (that can be overridden by a sub-implementation) of <code>gtk::Widget</code> was a lot. That caused people to avoid writing custom GTK widgets and do plenty of hacks like having one single <code>ObjectWrapper</code> GObject that would serialize a Rust struct into a <code>JSON</code> to store it as a string property in order to store these Rust types in a <code>gio::ListModel</code> and use it with <code>gtk::ListBox::bind_model</code>/<code>gtk::FlowBox::bind_model</code>.</p>
<p>Thankfully that is no longer the case for <code>gtk4-rs</code> as a huge amount of work went into manually implementing the necessary traits to support almost all of the types that can be subclassed with the exception of <code>gtk::TreeModel</code> (which would be deprecated starting from GTK 4.10), see <a rel="nofollow noreferrer" href="https://github.com/gtk-rs/gtk4-rs/pull/169">https://github.com/gtk-rs/gtk4-rs/pull/169</a> for details why that didn't happen yet.</p>
<p>As more people started writing custom GTK widgets/GStreamer plugins, more people looking into simplifying the whole experience grew.</p>
<p>Creating a very simple and useless custom GTK widget looked like this the days of gtk-rs-core 0.10:</p>
<pre data-lang="rust" style="background-color:#2e3440;color:#d8dee9;" class="language-rust "><code class="language-rust" data-lang="rust"><span style="color:#81a1c1;">mod </span><span>imp {
</span><span> </span><span style="color:#81a1c1;">pub struct </span><span style="color:#8fbcbb;">SimpleWidget</span><span style="color:#eceff4;">;
</span><span>
</span><span> </span><span style="color:#81a1c1;">impl </span><span>ObjectSubclass </span><span style="color:#81a1c1;">for </span><span>SimpleWidget {
</span><span> </span><span style="color:#81a1c1;">const </span><span>NAME</span><span style="color:#eceff4;">: </span><span style="color:#81a1c1;">&'static str = </span><span style="color:#a3be8c;">"SimpleWidget"</span><span style="color:#eceff4;">;
</span><span> </span><span style="color:#81a1c1;">type ParentType = </span><span>gtk</span><span style="color:#81a1c1;">::</span><span>Widget</span><span style="color:#eceff4;">;
</span><span> </span><span style="color:#81a1c1;">type Instance = </span><span>subclass</span><span style="color:#81a1c1;">::</span><span>simple</span><span style="color:#81a1c1;">::</span><span>InstanceStruct<</span><span style="color:#81a1c1;">Self</span><span>></span><span style="color:#eceff4;">;
</span><span> </span><span style="color:#81a1c1;">type Class = </span><span>subclass</span><span style="color:#81a1c1;">::</span><span>simple</span><span style="color:#81a1c1;">::</span><span>ClassStruct<</span><span style="color:#81a1c1;">Self</span><span>></span><span style="color:#eceff4;">;
</span><span>
</span><span> glib_object_subclass!()</span><span style="color:#eceff4;">;
</span><span>
</span><span> </span><span style="color:#81a1c1;">fn </span><span style="color:#88c0d0;">new</span><span>() </span><span style="color:#eceff4;">-> </span><span style="color:#81a1c1;">Self </span><span>{
</span><span> </span><span style="color:#81a1c1;">Self
</span><span> }
</span><span> }
</span><span>
</span><span> </span><span style="color:#81a1c1;">impl </span><span>ObjectImpl </span><span style="color:#81a1c1;">for </span><span>SimpleWidget {
</span><span> glib_object_impl!()</span><span style="color:#eceff4;">;
</span><span> }
</span><span> </span><span style="color:#81a1c1;">impl </span><span>WidgetImpl </span><span style="color:#81a1c1;">for </span><span>SimpleWidget {}
</span><span>}
</span><span>glib</span><span style="color:#81a1c1;">::</span><span>wrapper</span><span style="color:#81a1c1;">! </span><span>{
</span><span> </span><span style="color:#81a1c1;">pub struct </span><span style="color:#8fbcbb;">SimpleWidget</span><span>(ObjectSubclass<imp</span><span style="color:#81a1c1;">::</span><span>SimpleWidget>)
</span><span> </span><span style="color:#81a1c1;">@</span><span>extends gtk</span><span style="color:#81a1c1;">::</span><span>Widget</span><span style="color:#eceff4;">;
</span><span>}
</span></code></pre>
<p>Nowadays it looks like this:</p>
<pre data-lang="rust" style="background-color:#2e3440;color:#d8dee9;" class="language-rust "><code class="language-rust" data-lang="rust"><span style="color:#81a1c1;">mod </span><span>imp {
</span><span> #[derive(Default)]
</span><span> </span><span style="color:#81a1c1;">pub struct </span><span style="color:#8fbcbb;">SimpleWidget</span><span style="color:#eceff4;">;
</span><span>
</span><span> #[glib::object_subclass]
</span><span> </span><span style="color:#81a1c1;">impl </span><span>ObjectSubclass </span><span style="color:#81a1c1;">for </span><span>SimpleWidget {
</span><span> </span><span style="color:#81a1c1;">const </span><span>NAME</span><span style="color:#eceff4;">: </span><span style="color:#81a1c1;">&'static str = </span><span style="color:#a3be8c;">"SimpleWidget"</span><span style="color:#eceff4;">;
</span><span> </span><span style="color:#81a1c1;">type ParentType = </span><span>gtk</span><span style="color:#81a1c1;">::</span><span>Widget</span><span style="color:#eceff4;">;
</span><span> }
</span><span>
</span><span> </span><span style="color:#81a1c1;">impl </span><span>ObjectImpl </span><span style="color:#81a1c1;">for </span><span>SimpleWidget {}
</span><span> </span><span style="color:#81a1c1;">impl </span><span>WidgetImpl </span><span style="color:#81a1c1;">for </span><span>SimpleWidget {}
</span><span>}
</span><span>glib</span><span style="color:#81a1c1;">::</span><span>wrapper</span><span style="color:#81a1c1;">! </span><span>{
</span><span> </span><span style="color:#81a1c1;">pub struct </span><span style="color:#8fbcbb;">SimpleWidget</span><span>(ObjectSubclass<imp</span><span style="color:#81a1c1;">::</span><span>SimpleWidget>)
</span><span> </span><span style="color:#81a1c1;">@</span><span>extends gtk</span><span style="color:#81a1c1;">::</span><span>Widget</span><span style="color:#eceff4;">;
</span><span>}
</span></code></pre>
<p>Of course, the overall code is still a bit too verbose, particularly when you have to define GObject properties. However, people have been experimenting with writing a <a rel="nofollow noreferrer" href="https://github.com/gtk-rs/gtk-rs-core/pull/494">derive macro</a> to simplify the properties declaration part, as well as a <a rel="nofollow noreferrer" href="https://github.com/gtk-rs/gtk-rs-core/issues/540"><code>gobject::class!</code></a> macro for generating most of the remaining boilerplate code. Note, those macros are still experiments and would need more time to mature before eventually getting merged upstream.</p>
<h3 id="composite-templates">Composite Templates</h3>
<p>In short, composite templates allow you to make your custom <code>GtkWidget</code> subclass use GTK XML UI definitions for providing the widget structure, splitting the UI code from it logic. The UI part can be either inlined in the code, or written in a separate file, with optional runtime validation using the <code>xml_validation</code> feature (unless you are using <code>GResource</code>s).</p>
<pre data-lang="rust" style="background-color:#2e3440;color:#d8dee9;" class="language-rust "><code class="language-rust" data-lang="rust"><span style="color:#81a1c1;">mod </span><span>imp {
</span><span> #[derive(Default</span><span style="color:#eceff4;">,</span><span> gtk::CompositeTemplate)]
</span><span> #[template(string </span><span style="color:#81a1c1;">= r</span><span style="color:#a3be8c;">#"
</span><span style="color:#a3be8c;"> <interface>
</span><span style="color:#a3be8c;"> <template class="SimpleWidget" parent="GtkWidget">
</span><span style="color:#a3be8c;"> <child>
</span><span style="color:#a3be8c;"> <object class="GtkLabel" id="label">
</span><span style="color:#a3be8c;"> <property name="label">foobar</property>
</span><span style="color:#a3be8c;"> </object>
</span><span style="color:#a3be8c;"> </child>
</span><span style="color:#a3be8c;"> </template>
</span><span style="color:#a3be8c;"> </interface>
</span><span style="color:#a3be8c;"> "#</span><span>)]
</span><span> </span><span style="color:#81a1c1;">pub struct </span><span style="color:#8fbcbb;">SimpleWidget </span><span>{
</span><span> #[template_child]
</span><span> </span><span style="color:#81a1c1;">pub </span><span>label</span><span style="color:#eceff4;">: </span><span>TemplateChild<gtk</span><span style="color:#81a1c1;">::</span><span>Label>,
</span><span> }
</span><span>
</span><span> #[glib::object_subclass]
</span><span> </span><span style="color:#81a1c1;">impl </span><span>ObjectSubclass </span><span style="color:#81a1c1;">for </span><span>SimpleWidget {
</span><span> </span><span style="color:#81a1c1;">const </span><span>NAME</span><span style="color:#eceff4;">: </span><span style="color:#81a1c1;">&'static str = </span><span style="color:#a3be8c;">"SimpleWidget"</span><span style="color:#eceff4;">;
</span><span> </span><span style="color:#81a1c1;">type ParentType = </span><span>gtk</span><span style="color:#81a1c1;">::</span><span>Widget</span><span style="color:#eceff4;">;
</span><span>
</span><span> </span><span style="color:#81a1c1;">fn </span><span style="color:#88c0d0;">class_init</span><span>(klass</span><span style="color:#eceff4;">: </span><span style="color:#81a1c1;">&mut Self::</span><span>Class) {
</span><span> klass</span><span style="color:#81a1c1;">.</span><span style="color:#88c0d0;">bind_template</span><span>()</span><span style="color:#eceff4;">;
</span><span> }
</span><span>
</span><span> </span><span style="color:#81a1c1;">fn </span><span style="color:#88c0d0;">instance_init</span><span>(obj</span><span style="color:#eceff4;">: </span><span style="color:#81a1c1;">&</span><span>gtk</span><span style="color:#81a1c1;">::</span><span>glib</span><span style="color:#81a1c1;">::</span><span>subclass</span><span style="color:#81a1c1;">::</span><span>InitializingObject<</span><span style="color:#81a1c1;">Self</span><span>>) {
</span><span> obj</span><span style="color:#81a1c1;">.</span><span style="color:#88c0d0;">init_template</span><span>()</span><span style="color:#eceff4;">;
</span><span> }
</span><span> }
</span><span>
</span><span> </span><span style="color:#81a1c1;">impl </span><span>ObjectImpl </span><span style="color:#81a1c1;">for </span><span>SimpleWidget {
</span><span> </span><span style="color:#81a1c1;">fn </span><span style="color:#88c0d0;">dispose</span><span>(</span><span style="color:#81a1c1;">&</span><span>self) {
</span><span> </span><span style="color:#616e88;">// since gtk 4.8 and only if you are using composite templates
</span><span> </span><span style="color:#81a1c1;">self.</span><span style="color:#88c0d0;">dispose_template</span><span>()</span><span style="color:#eceff4;">;
</span><span> </span><span style="color:#616e88;">// before gtk 4.8, for each direct child widget
</span><span> </span><span style="color:#616e88;">// while let Some(child) = self.obj().first_child() {
</span><span> </span><span style="color:#616e88;">// child.unparent();
</span><span> </span><span style="color:#616e88;">// }
</span><span> }
</span><span> }
</span><span> </span><span style="color:#81a1c1;">impl </span><span>WidgetImpl </span><span style="color:#81a1c1;">for </span><span>SimpleWidget {}
</span><span>}
</span><span>glib</span><span style="color:#81a1c1;">::</span><span>wrapper</span><span style="color:#81a1c1;">! </span><span>{
</span><span> </span><span style="color:#81a1c1;">pub struct </span><span style="color:#8fbcbb;">SimpleWidget</span><span>(ObjectSubclass<imp</span><span style="color:#81a1c1;">::</span><span>SimpleWidget>)
</span><span> </span><span style="color:#81a1c1;">@</span><span>extends gtk</span><span style="color:#81a1c1;">::</span><span>Widget</span><span style="color:#eceff4;">;
</span><span>}
</span></code></pre>
<p>Composite templates also allow you to also set a function to be called when a specific signal is emitted:</p>
<pre data-lang="rust" style="background-color:#2e3440;color:#d8dee9;" class="language-rust "><code class="language-rust" data-lang="rust"><span style="color:#81a1c1;">mod </span><span>imp {
</span><span> #[derive(Default</span><span style="color:#eceff4;">,</span><span> gtk::CompositeTemplate)]
</span><span> #[template(string </span><span style="color:#81a1c1;">= r</span><span style="color:#a3be8c;">#"
</span><span style="color:#a3be8c;"> <interface>
</span><span style="color:#a3be8c;"> <template class="SimpleWidget" parent="GtkWidget">
</span><span style="color:#a3be8c;"> <child>
</span><span style="color:#a3be8c;"> <object class="GtkButton" id="button">
</span><span style="color:#a3be8c;"> <property name="label">Click me!</property>
</span><span style="color:#a3be8c;"> <signal name="clicked" handler="on_clicked" swapped="true" />
</span><span style="color:#a3be8c;"> </object>
</span><span style="color:#a3be8c;"> </child>
</span><span style="color:#a3be8c;"> </template>
</span><span style="color:#a3be8c;"> </interface>
</span><span style="color:#a3be8c;"> "#</span><span>)]
</span><span> </span><span style="color:#81a1c1;">pub struct </span><span style="color:#8fbcbb;">SimpleWidget </span><span>{
</span><span> #[template_child]
</span><span> </span><span style="color:#81a1c1;">pub </span><span>label</span><span style="color:#eceff4;">: </span><span>TemplateChild<gtk</span><span style="color:#81a1c1;">::</span><span>Label>,
</span><span> }
</span><span>
</span><span> #[glib::object_subclass]
</span><span> </span><span style="color:#81a1c1;">impl </span><span>ObjectSubclass </span><span style="color:#81a1c1;">for </span><span>SimpleWidget {
</span><span> </span><span style="color:#81a1c1;">const </span><span>NAME</span><span style="color:#eceff4;">: </span><span style="color:#81a1c1;">&'static str = </span><span style="color:#a3be8c;">"SimpleWidget"</span><span style="color:#eceff4;">;
</span><span> </span><span style="color:#81a1c1;">type ParentType = </span><span>gtk</span><span style="color:#81a1c1;">::</span><span>Widget</span><span style="color:#eceff4;">;
</span><span>
</span><span> </span><span style="color:#81a1c1;">fn </span><span style="color:#88c0d0;">class_init</span><span>(klass</span><span style="color:#eceff4;">: </span><span style="color:#81a1c1;">&mut Self::</span><span>Class) {
</span><span> klass</span><span style="color:#81a1c1;">.</span><span style="color:#88c0d0;">bind_template</span><span>()</span><span style="color:#eceff4;">;
</span><span> klass</span><span style="color:#81a1c1;">.</span><span style="color:#88c0d0;">bind_template_instance_callbacks</span><span>()</span><span style="color:#eceff4;">;
</span><span> }
</span><span>
</span><span> </span><span style="color:#81a1c1;">fn </span><span style="color:#88c0d0;">instance_init</span><span>(obj</span><span style="color:#eceff4;">: </span><span style="color:#81a1c1;">&</span><span>gtk</span><span style="color:#81a1c1;">::</span><span>glib</span><span style="color:#81a1c1;">::</span><span>subclass</span><span style="color:#81a1c1;">::</span><span>InitializingObject<</span><span style="color:#81a1c1;">Self</span><span>>) {
</span><span> obj</span><span style="color:#81a1c1;">.</span><span style="color:#88c0d0;">init_template</span><span>()</span><span style="color:#eceff4;">;
</span><span> }
</span><span> }
</span><span>
</span><span> </span><span style="color:#81a1c1;">impl </span><span>ObjectImpl </span><span style="color:#81a1c1;">for </span><span>SimpleWidget {
</span><span> </span><span style="color:#81a1c1;">fn </span><span style="color:#88c0d0;">dispose</span><span>(</span><span style="color:#81a1c1;">&</span><span>self) {
</span><span> </span><span style="color:#616e88;">// since gtk 4.8 and only if you are using composite templates
</span><span> </span><span style="color:#81a1c1;">self.</span><span style="color:#88c0d0;">dispose_template</span><span>()</span><span style="color:#eceff4;">;
</span><span> </span><span style="color:#616e88;">// before gtk 4.8, for each direct child widget
</span><span> </span><span style="color:#616e88;">// while let Some(child) = self.obj().first_child() {
</span><span> </span><span style="color:#616e88;">// child.unparent();
</span><span> </span><span style="color:#616e88;">// }
</span><span> }
</span><span> }
</span><span> </span><span style="color:#81a1c1;">impl </span><span>WidgetImpl </span><span style="color:#81a1c1;">for </span><span>SimpleWidget {}
</span><span>}
</span><span>glib</span><span style="color:#81a1c1;">::</span><span>wrapper</span><span style="color:#81a1c1;">! </span><span>{
</span><span> </span><span style="color:#81a1c1;">pub struct </span><span style="color:#8fbcbb;">SimpleWidget</span><span>(ObjectSubclass<imp</span><span style="color:#81a1c1;">::</span><span>SimpleWidget>)
</span><span> </span><span style="color:#81a1c1;">@</span><span>extends gtk</span><span style="color:#81a1c1;">::</span><span>Widget</span><span style="color:#eceff4;">;
</span><span>}
</span><span>
</span><span>#[gtk::template_callbacks]
</span><span style="color:#81a1c1;">impl </span><span>SimpleWidget {
</span><span> #[template_callback]
</span><span> </span><span style="color:#81a1c1;">fn </span><span style="color:#88c0d0;">on_clicked</span><span>(</span><span style="color:#81a1c1;">&</span><span>self, _button</span><span style="color:#eceff4;">: </span><span style="color:#81a1c1;">&</span><span>gtk</span><span style="color:#81a1c1;">::</span><span>Button) {
</span><span> println!(</span><span style="color:#a3be8c;">"Clicked!"</span><span>)</span><span style="color:#eceff4;">;
</span><span> }
</span><span>}
</span></code></pre>
<p>More details can be found in <a rel="nofollow noreferrer" href="https://gtk-rs.org/gtk4-rs/stable/latest/docs/gtk4_macros/derive.CompositeTemplate.html">https://gtk-rs.org/gtk4-rs/stable/latest/docs/gtk4_macros/derive.CompositeTemplate.html</a> and <a rel="nofollow noreferrer" href="https://gtk-rs.org/gtk4-rs/stable/latest/docs/gtk4_macros/attr.template_callbacks.html">https://gtk-rs.org/gtk4-rs/stable/latest/docs/gtk4_macros/attr.template_callbacks.html</a>.</p>
<h3 id="book-documentations-examples">Book, Documentations, Examples</h3>
<p>Great bindings by themselves sadly won't help newcomers trying to learn GTK for the first time, despite the increasing number of apps written using <code>gtk4-rs</code>.</p>
<p>For that reason, Julian Hofer has been writing a "GUI development with Rust and GTK 4" book that you can find at <a rel="nofollow noreferrer" href="https://gtk-rs.org/gtk4-rs/stable/latest/book/">https://gtk-rs.org/gtk4-rs/stable/latest/book/</a>.</p>
<p>On top of that, we spend a good amount of time ensuring the documentation we provide, which is based on the C library documentation, has valid intra-links, uses the images provided by GTK C documentation to represent the various widgets, and that most of the types and functions are properly documented.</p>
<p>The <code>gtk4-rs</code> repository also includes ~35 examples that you can find in <a rel="nofollow noreferrer" href="https://github.com/gtk-rs/gtk4-rs/tree/master/examples">https://github.com/gtk-rs/gtk4-rs/tree/master/examples</a>. Finally there's a <code>GTK & Rust</code> application repository template that you can use to get started with your next project: <a rel="nofollow noreferrer" href="https://gitlab.gnome.org/World/Rust/gtk-rust-template">https://gitlab.gnome.org/World/Rust/gtk-rust-template</a>.</p>
<h2 id="flatpak-rust-sdks">Flatpak & Rust SDKs</h2>
<p>Beyond the building blocks provided by the bindings, we have also worked on providing stable and nightly Rust SDKs that can be used to either distribute your application as a Flatpak or as a development environment. The stable SDK comes with <a rel="nofollow noreferrer" href="https://github.com/rui314/mold">the Mold linker</a> pre-installed as well, which is recommended for improving build times.</p>
<p>Flatpak can be used as a development environment with either <a rel="nofollow noreferrer" href="https://flathub.org/apps/details/org.gnome.Builder">GNOME Builder</a>, VSCode with the <a rel="nofollow noreferrer" href="https://marketplace.visualstudio.com/items?itemName=bilelmoussaoui.flatpak-vscode">flatpak-vscode</a> extension, or <a rel="nofollow noreferrer" href="https://gitlab.gnome.org/ZanderBrown/fenv/">fenv</a>, if you prefer the CLI for building/running your application.</p>
<ul>
<li><a rel="nofollow noreferrer" href="https://github.com/flathub/org.freedesktop.Sdk.Extension.rust-stable">https://github.com/flathub/org.freedesktop.Sdk.Extension.rust-stable</a></li>
<li><a rel="nofollow noreferrer" href="https://github.com/flathub/org.freedesktop.Sdk.Extension.rust-nightly">https://github.com/flathub/org.freedesktop.Sdk.Extension.rust-nightly</a></li>
</ul>
<h2 id="portals">Portals</h2>
<p>Modern Linux applications should make use of the <a rel="nofollow noreferrer" href="https://flatpak.github.io/xdg-desktop-portal/">portals</a> to request runtime permissions to access resources, such as capturing the camera feed, starting a screencast session or picking a file. </p>
<p><a rel="nofollow noreferrer" href="https://crates.io/crates/ashpd">ASHPD</a> is currently the way to go for using portals from Rust as it provides a convenient and idomatic API on top of the DBus one.</p>
<p>Code example from <code>ashpd-0.4.0-alpha.4</code> for using the color picker in a desktop environment agnostic way:</p>
<pre data-lang="rust" style="background-color:#2e3440;color:#d8dee9;" class="language-rust "><code class="language-rust" data-lang="rust"><span style="color:#81a1c1;">use </span><span>ashpd</span><span style="color:#81a1c1;">::</span><span>desktop</span><span style="color:#81a1c1;">::</span><span>screenshot</span><span style="color:#81a1c1;">::</span><span>Color</span><span style="color:#eceff4;">;
</span><span>
</span><span>async </span><span style="color:#81a1c1;">fn </span><span style="color:#88c0d0;">run</span><span>() </span><span style="color:#eceff4;">-> </span><span>ashpd</span><span style="color:#81a1c1;">::</span><span style="color:#8fbcbb;">Result</span><span><()> {
</span><span> </span><span style="color:#81a1c1;">let</span><span> color </span><span style="color:#81a1c1;">= </span><span>Color</span><span style="color:#81a1c1;">::</span><span>builder()</span><span style="color:#81a1c1;">.</span><span style="color:#88c0d0;">build</span><span>()</span><span style="color:#81a1c1;">.</span><span>await</span><span style="color:#81a1c1;">?.</span><span style="color:#88c0d0;">response</span><span>()</span><span style="color:#81a1c1;">?</span><span style="color:#eceff4;">;
</span><span> println!(</span><span style="color:#a3be8c;">"(</span><span style="color:#ebcb8b;">{}</span><span style="color:#a3be8c;">, </span><span style="color:#ebcb8b;">{}</span><span style="color:#a3be8c;">, </span><span style="color:#ebcb8b;">{}</span><span style="color:#a3be8c;">)"</span><span style="color:#eceff4;">,</span><span> color</span><span style="color:#81a1c1;">.</span><span style="color:#88c0d0;">red</span><span>()</span><span style="color:#eceff4;">,</span><span> color</span><span style="color:#81a1c1;">.</span><span style="color:#88c0d0;">green</span><span>()</span><span style="color:#eceff4;">,</span><span> color</span><span style="color:#81a1c1;">.</span><span style="color:#88c0d0;">blue</span><span>())</span><span style="color:#eceff4;">;
</span><span> </span><span style="color:#8fbcbb;">Ok</span><span>(())
</span><span>}
</span></code></pre>
<h2 id="keyring-oo7">Keyring & oo7</h2>
<p>Applications that need to store sensitive information, such as passwords, usually use the <a rel="nofollow noreferrer" href="https://specifications.freedesktop.org/secret-service/latest/">Secret service</a>, a protocol supported by both <code>gnome-keyring</code> and <code>kwallet</code> nowadays. There were multiple attempts to provide a Rust wrapper for the DBus API but some common pitfalls were that they lacked async support, provided no integration with the secret portal, or they had no way to allow applications to migrate their secrets from the host keyring to the application sandboxed keyring.</p>
<p>Those were the primary reasons we started working on <a rel="nofollow noreferrer" href="https://crates.io/crates/oo7">oo7</a>. It is still in alpha stage, but should cover most of the use cases already.</p>
<p>Since 0.1.0-beta.3, oo7 has a new feature for using OpenSSL for cryptographic primitives instead of Rust crypto crates.</p>
<h2 id="gstreamer">GStreamer</h2>
<p>Part of the experiments I did when porting <a rel="nofollow noreferrer" href="https://gitlab.gnome.org/World/Authenticator">Authenticator</a> to GTK 4 was figuring out how I can replicate the internal GStreamer sink included in GTK to convert a video frame into a <code>gdk::MemoryTexture</code> in order to render it in some widget for QR code scanning purposes.</p>
<p>Jordan Petridis took over my WIP work and turned it into a proper GStreamer plugin written in Rust, see <a rel="nofollow noreferrer" href="https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs/-/tree/main/video/gtk4">https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs/-/tree/main/video/gtk4</a> for an example on how to use it in your application.</p>
<h2 id="missing-integrations">Missing Integrations</h2>
<h3 id="better-gettext-support">Better Gettext Support</h3>
<p>Currently, <a rel="nofollow noreferrer" href="https://www.gnu.org/software/gettext/">gettext</a> doesn't support Rust officially, and most importantly, it doesn't like the <code>!</code> character, that is used by declarative macros in Rust, such as <code>println!</code> and <code>format!</code>, so we can't use string formatting for translatable texts. </p>
<p>Kévin Commaille has submitted a <a rel="nofollow noreferrer" href="https://savannah.gnu.org/bugs/?56774">patch</a> for upstream gettext but sadly it hasn't been reviewed yet :( For now people are working around this by manually replacing variable names with <a rel="nofollow noreferrer" href="https://doc.rust-lang.org/std/primitive.str.html#method.replace">https://doc.rust-lang.org/std/primitive.str.html#method.replace</a>, which is not ideal, but it is what we have for now.</p>
<h3 id="reduced-boilerplate-in-subclassing-code">Reduced Boilerplate in Subclassing Code</h3>
<p>As mentioned above, subclassing code is still too verbose in some cases. Ideally, we would simplify most of it, since it is probably one of the most confusing things you have to deal with as a beginner to the gtk-rs ecosystem.</p>
<ul>
<li>A <a rel="nofollow noreferrer" href="https://gtk-rs.org/gtk-rs-core/stable/0.17/docs/glib/derive.Properties.html">glib::Properties</a> derive macro is available on the glib crate as part of the 0.17 release</li>
</ul>
<h3 id="even-better-documentation">Even Better Documentation</h3>
<p>I personally think our documentation has gotten a lot better in the last couple of releases but there are always things to improve. Here is my wishlist of things that I hope to find the time to work on for the next release:</p>
<ul>
<li><del>Generate properties/signals documentation <a rel="nofollow noreferrer" href="https://github.com/gtk-rs/gir/issues/1076">https://github.com/gtk-rs/gir/issues/1076</a></del></li>
<li>Basic subclassing code documentation <a rel="nofollow noreferrer" href="https://github.com/gtk-rs/gir/issues/1319">https://github.com/gtk-rs/gir/issues/1319</a></li>
<li>Correct linking of external crates in case of manually implemented functions <a rel="nofollow noreferrer" href="https://github.com/gtk-rs/gir/issues/1197">https://github.com/gtk-rs/gir/issues/1197</a></li>
<li>Generate a UML graph for the object hierarchy <a rel="nofollow noreferrer" href="https://github.com/gtk-rs/gir/issues/1035">https://github.com/gtk-rs/gir/issues/1035</a></li>
</ul>
<p>Feel free to pick any of the above issues if you would like to help.</p>
<h3 id="automatically-generated-subclassing-traits">Automatically-Generated Subclassing Traits</h3>
<p>Presently, we have to manually write all the necessary traits needed for making it possible to subclass a type or implement an interface.</p>
<p>Sadly, we can't do much about it today as we would need new gobject-introspection annotations, see e.g. <a rel="nofollow noreferrer" href="https://gitlab.gnome.org/GNOME/gobject-introspection/-/issues/411">https://gitlab.gnome.org/GNOME/gobject-introspection/-/issues/411</a>.</p>
<h3 id="fix-gnome-ci-template-to-avoid-duplicated-builds">Fix GNOME CI Template to Avoid Duplicated Builds</h3>
<p>Most of the GNOME applications hosted on <a rel="nofollow noreferrer" href="https://gitlab.gnome.org">https://gitlab.gnome.org</a> are using <a rel="nofollow noreferrer" href="https://gitlab.gnome.org/GNOME/citemplates/">https://gitlab.gnome.org/GNOME/citemplates/</a> as a CI template to provide a Flatpak job. The template is inefficient when building a Rust application as it removes the Flatpak repository between a regular build and a test build, which means rebuilding all the crates a second time, see <a rel="nofollow noreferrer" href="https://gitlab.gnome.org/GNOME/citemplates/-/issues/5">https://gitlab.gnome.org/GNOME/citemplates/-/issues/5</a>. <code>flatpak-builder</code> itself already provides an easy way to run the tests of a specific module, which is what <code>flatpak-github-actions</code> uses, but I am yet to find the time and apply the same thing to GNOME's CI template.</p>
<h2 id="special-thanks">Special Thanks</h2>
<ul>
<li>Julian Hofer for the gtk4-rs book</li>
<li><a rel="nofollow noreferrer" href="https://blogs.gnome.org/christopherdavis/">Christopher Davis</a>, Jason Francis, Paolo Borelli for their work on composite templates macros</li>
<li><a rel="nofollow noreferrer" href="https://blogs.gnome.org/sophieh/">Sophie Herold</a> for her awesome work implementing the portal backend of oo7</li>
<li><a rel="nofollow noreferrer" href="http://zee-nix.blogspot.com/">Zeeshan</a> for zbus/zvariant which unlocked plenty of use cases</li>
<li>wayland-rs developers for making it possible to integrate Wayland clients with ASHPD</li>
<li>Every other contributor to the gtk-rs ecosystem</li>
<li>Ivan Molodetskikh, Sebastian Dröge and Christopher Davis for reviewing this post</li>
</ul>
Trying out Zola2022-10-10T00:00:00+00:002022-10-10T00:00:00+00:00
Unknown
https://belmoussaoui.com/blog/14-trying-out-zola/<p>For nearly two years I have been inactive on my blog despite spending time making a <em>fancy</em> website, but I can no longer afford the extra code to maintain and infrastructure work to keep it running. So I decided to move the posts I had on the old website to a statically generated one while waiting for the CI to pass during the <a rel="nofollow noreferrer" href="https://wiki.gnome.org/Hackfests/Rust2022">gtk-rs hackfest</a>.</p>
<p>One of the annoyances I had with static websites generators is that they were too slow. Add to that <a rel="nofollow noreferrer" href="https://jekyllrb.com/">Jekyll</a> is written in Ruby and it was too difficult to get any <code>rubygem</code> installed on my machine.</p>
<h2 id="enters-zola">Enters Zola</h2>
<p>Per the project description, <a rel="nofollow noreferrer" href="https://www.getzola.org/">Zola</a> is a "A fast static site generator in a single binary with everything built-in".</p>
<h3 id="installation">Installation</h3>
<p>The installation process is pretty easy using <code>cargo</code> </p>
<pre data-lang="bash" style="background-color:#2e3440;color:#d8dee9;" class="language-bash "><code class="language-bash" data-lang="bash"><span style="color:#88c0d0;">cargo</span><span> install zola
</span></code></pre>
<p>You could also use the pre-built binaries if you want to avoid waiting a bit of time for everything to compile locally, see <a rel="nofollow noreferrer" href="https://www.getzola.org/documentation/getting-started/installation/">https://www.getzola.org/documentation/getting-started/installation/</a>.</p>
<p>Once Zola is installed, you can initialize a new project:</p>
<pre data-lang="bash" style="background-color:#2e3440;color:#d8dee9;" class="language-bash "><code class="language-bash" data-lang="bash"><span style="color:#88c0d0;">zola</span><span> init my-new-blog
</span></code></pre>
<p>To run the web server and test your changes:</p>
<pre data-lang="bash" style="background-color:#2e3440;color:#d8dee9;" class="language-bash "><code class="language-bash" data-lang="bash"><span style="color:#88c0d0;">zola</span><span> serve
</span></code></pre>
<p>Then you either have the choice to write your template/theme based on your needs or if you want something that just works like me, you will probably be served by <a rel="nofollow noreferrer" href="https://www.getzola.org/themes/">https://www.getzola.org/themes/</a>.</p>
<h3 id="writing-process">Writing process</h3>
<p>That would depend on the theme you ended up picking, in the case of <a rel="nofollow noreferrer" href="https://github.com/isunjn/serene">Serene</a>, all I had to do is create a <code>blog</code> directory inside <code>content</code> and add a <code>markdown</code> file for each post.</p>
<p>A very helpful feature I noticed while porting my articles to <code>markdown</code>, that validates all the links on your posts is</p>
<pre data-lang="bash" style="background-color:#2e3440;color:#d8dee9;" class="language-bash "><code class="language-bash" data-lang="bash"><span style="color:#88c0d0;">zola</span><span> check
</span></code></pre>
<h3 id="publishing">Publishing</h3>
<p>The simplest way of publishing a static website nowadays is to use something like Github/Gitlab Pages. The documentation got you covered as they include the <code>yaml</code> receipe for the popular services out there <a rel="nofollow noreferrer" href="https://www.getzola.org/documentation/deployment/overview/">https://www.getzola.org/documentation/deployment/overview/</a>.</p>
<p>Although, when using the Github receipe, it seems there is an error in the documentation and you will have to do the following change for it to work</p>
<pre data-lang="diff" style="background-color:#2e3440;color:#d8dee9;" class="language-diff "><code class="language-diff" data-lang="diff"><span style="color:#bf616a;">- TOKEN: ${{ secrets.GITHUB_TOKEN }}
</span><span style="color:#a3be8c;">+ TOKEN: $GITHUB_ACTOR:${{ secrets.GITHUB_TOKEN }}
</span></code></pre>
<h2 id="conclusion">Conclusion</h2>
<p>As you might have noticed, I managed to migrate all the content I had on the old website a few hours after installing Zola for the first time. The whole process is pretty smooth if you are familiar with static website generators.</p>
<p>If you are looking for a Jekyll/Hugo replacement, I highly recommend you to give Zola a try.</p>
Integrating codespell into your CI2020-10-02T00:00:00+00:002020-10-02T00:00:00+00:00
Unknown
https://belmoussaoui.com/blog/13-integrate-codespell-ci/<p>If you have never heard of <a rel="nofollow noreferrer" href="https://github.com/codespell-project/codespell">codespell</a>, it's a command line utility to check for common misspellings with the possibility to add your own dictionaries. I have looked lately at integrating it as part of the CI for some of my projects. </p>
<h1 id="gitlab-ci">Gitlab CI</h1>
<p>Currently codespell doesn't provide a docker image that could be used for CI. You can create your own or use a pretty simple <a rel="nofollow noreferrer" href="https://hub.docker.com/r/bilelmoussaoui/codespell">image</a> I have setup and pushed to <a rel="nofollow noreferrer" href="https://hub.docker.com">https://hub.docker.com</a>. </p>
<p>The docker image is a simple as </p>
<pre data-lang="dockerfile" style="background-color:#2e3440;color:#d8dee9;" class="language-dockerfile "><code class="language-dockerfile" data-lang="dockerfile"><span style="color:#81a1c1;">FROM</span><span> python:</span><span style="color:#8fbcbb;">3.8
</span><span>
</span><span style="color:#81a1c1;">RUN </span><span>pip3 install codespell
</span><span>
</span><span>Once you have an image up and ready, you can add it to your .gitlab-ci.yml
</span><span>
</span><span>codespell:
</span><span> image: "docker.io/bilelmoussaoui/codespell"
</span><span> script:
</span><span> - codespell -S "*.png,*.po,.git,*.jpg" -f
</span></code></pre>
<p><code>-S</code> argument is a comma separated glob pattern, directories or files to ignore. The <code>-f</code> argument makes codespell check the file names as well.</p>
<p>You can find more about the possible options you can pass by running <code>codepsell --help</code></p>
<h1 id="github-actions">Github Actions</h1>
<p>Codespell provides a <a rel="nofollow noreferrer" href="https://github.com/codespell-project/actions-codespell">Github Action</a> so integrating it as a workflow should be straightforward </p>
<pre data-lang="yaml" style="background-color:#2e3440;color:#d8dee9;" class="language-yaml "><code class="language-yaml" data-lang="yaml"><span style="color:#81a1c1;">on</span><span style="color:#eceff4;">:
</span><span> </span><span style="color:#8fbcbb;">push</span><span style="color:#eceff4;">:
</span><span> </span><span style="color:#8fbcbb;">branches</span><span style="color:#eceff4;">: </span><span>[</span><span style="color:#a3be8c;">master</span><span>]
</span><span> </span><span style="color:#8fbcbb;">pull_request</span><span style="color:#eceff4;">:
</span><span>
</span><span style="color:#8fbcbb;">name</span><span style="color:#eceff4;">: </span><span style="color:#a3be8c;">Spell Check
</span><span>
</span><span style="color:#8fbcbb;">jobs</span><span style="color:#eceff4;">:
</span><span> </span><span style="color:#8fbcbb;">codespell</span><span style="color:#eceff4;">:
</span><span> </span><span style="color:#8fbcbb;">runs-on</span><span style="color:#eceff4;">: </span><span style="color:#a3be8c;">ubuntu-latest
</span><span> </span><span style="color:#8fbcbb;">steps</span><span style="color:#eceff4;">:
</span><span> - </span><span style="color:#8fbcbb;">uses</span><span style="color:#eceff4;">: </span><span style="color:#a3be8c;">actions/checkout@v2
</span><span> - </span><span style="color:#8fbcbb;">uses</span><span style="color:#eceff4;">: </span><span style="color:#a3be8c;">codespell-project/actions-codespell@master
</span><span> </span><span style="color:#8fbcbb;">with</span><span style="color:#eceff4;">:
</span><span> </span><span style="color:#8fbcbb;">check_filenames</span><span style="color:#eceff4;">: </span><span style="color:#81a1c1;">true
</span></code></pre>
<p>See the <a rel="nofollow noreferrer" href="https://github.com/codespell-project/actions-codespell/blob/master/action.yml#L5">list of arguments</a> you can pass to the action.</p>
<p><a rel="nofollow noreferrer" href="https://github.com/codespell-project/codespell/blob/master/codespell_lib/_codespell.py#L53">By default</a> codespell uses two dictionaries "clear" and "rare". You can tweak <a rel="nofollow noreferrer" href="https://github.com/codespell-project/codespell/blob/master/codespell_lib/_codespell.py#L47">that list</a> to use something else by passing <code>--builtin "clear,usage,code"</code>.</p>
Oxidizing portals with zbus2020-09-13T00:00:00+00:002020-09-13T00:00:00+00:00
Unknown
https://belmoussaoui.com/blog/12-oxidizing-portals/<p>One major pain points of writing a desktop application that interacts with the user's desktop is that a "simple" task can easily become complex. If you want to write a colour palette generator and you wanted to pick a colour, how would you do that? </p>
<p>GNOME Shell for example provides a DBus interface <code>org.gnome.Shell.Screenshot</code> that you can communicate with by calling the <code>PickColor</code> method. The method returns a HashMap containing a single key <code>{"color" : [f64;3] }</code>. Thankfully with zbus calling a DBus method is pretty straightforward. </p>
<pre data-lang="rust" style="background-color:#2e3440;color:#d8dee9;" class="language-rust "><code class="language-rust" data-lang="rust"><span style="color:#81a1c1;">use </span><span>zbus</span><span style="color:#81a1c1;">::</span><span>dbus_proxy</span><span style="color:#eceff4;">;
</span><span style="color:#81a1c1;">use </span><span>zvariant</span><span style="color:#81a1c1;">::</span><span>OwnedValue</span><span style="color:#eceff4;">;
</span><span>
</span><span>#[dbus_proxy(interface </span><span style="color:#81a1c1;">= </span><span style="color:#a3be8c;">"org.gnome.Shell.Screenshot"</span><span style="color:#eceff4;">,</span><span> default_path </span><span style="color:#81a1c1;">= </span><span style="color:#a3be8c;">"/org/gnome/Shell/Screenshot"</span><span>)]
</span><span style="color:#81a1c1;">trait </span><span style="color:#8fbcbb;">Screenshot </span><span>{
</span><span> </span><span style="color:#616e88;">// zbus converts the method names from PascalCase to snake_case.
</span><span> </span><span style="color:#81a1c1;">fn </span><span style="color:#88c0d0;">pick_color</span><span>(</span><span style="color:#81a1c1;">&</span><span>self) </span><span style="color:#eceff4;">-> </span><span>zbus</span><span style="color:#81a1c1;">::</span><span style="color:#8fbcbb;">Result</span><span><HashMap<</span><span style="color:#8fbcbb;">String</span><span>, OwnedValue>></span><span style="color:#eceff4;">;
</span><span>}
</span></code></pre>
<p>By using the <a rel="nofollow noreferrer" href="https://docs.rs/zbus/1.1.1/zbus/attr.dbus_proxy.html">dbus_proxy</a> macro, we can generate a <a rel="nofollow noreferrer" href="https://docs.rs/zbus/1.1.1/zbus/struct.Proxy.html">Proxy</a> containing the only method we care about. We can then use our auto generated <code>ScreenshotProxy</code> to call the pick colour method </p>
<pre data-lang="rust" style="background-color:#2e3440;color:#d8dee9;" class="language-rust "><code class="language-rust" data-lang="rust"><span style="color:#81a1c1;">fn </span><span style="color:#88c0d0;">main</span><span>() </span><span style="color:#eceff4;">-> </span><span>zbus</span><span style="color:#81a1c1;">::</span><span>fdo</span><span style="color:#81a1c1;">::</span><span style="color:#8fbcbb;">Result</span><span><()> {
</span><span> </span><span style="color:#81a1c1;">let</span><span> connection </span><span style="color:#81a1c1;">= </span><span>zbus</span><span style="color:#81a1c1;">::</span><span>Connection</span><span style="color:#81a1c1;">::</span><span>new_session()</span><span style="color:#81a1c1;">?</span><span style="color:#eceff4;">;
</span><span> </span><span style="color:#81a1c1;">let</span><span> proxy </span><span style="color:#81a1c1;">= </span><span>ScreenshotProxy</span><span style="color:#81a1c1;">::</span><span>new(</span><span style="color:#81a1c1;">&</span><span>connection)</span><span style="color:#81a1c1;">?</span><span style="color:#eceff4;">;
</span><span>
</span><span> </span><span style="color:#81a1c1;">let</span><span> reply </span><span style="color:#81a1c1;">=</span><span> proxy</span><span style="color:#81a1c1;">.</span><span style="color:#88c0d0;">pick_color</span><span>()</span><span style="color:#81a1c1;">?</span><span style="color:#eceff4;">;
</span><span> println!(</span><span style="color:#a3be8c;">"</span><span style="color:#ebcb8b;">{:#?}</span><span style="color:#a3be8c;">"</span><span style="color:#eceff4;">,</span><span> reply)</span><span style="color:#eceff4;">;
</span><span> </span><span style="color:#616e88;">// You can grab the color as Vec<f64> using
</span><span> </span><span style="color:#81a1c1;">let</span><span> color </span><span style="color:#81a1c1;">=</span><span> reply
</span><span> </span><span style="color:#81a1c1;">.</span><span style="color:#88c0d0;">get</span><span>(</span><span style="color:#a3be8c;">"color"</span><span>)
</span><span> </span><span style="color:#81a1c1;">.</span><span style="color:#88c0d0;">unwrap</span><span>()
</span><span> </span><span style="color:#81a1c1;">.</span><span>downcast_ref</span><span style="color:#81a1c1;">::</span><span><zvariant</span><span style="color:#81a1c1;">::</span><span>Structure>()
</span><span> </span><span style="color:#81a1c1;">.</span><span style="color:#88c0d0;">unwrap</span><span>()
</span><span> </span><span style="color:#81a1c1;">.</span><span style="color:#88c0d0;">fields</span><span>()
</span><span> </span><span style="color:#81a1c1;">.</span><span style="color:#88c0d0;">iter</span><span>()
</span><span> </span><span style="color:#81a1c1;">.</span><span style="color:#88c0d0;">map</span><span>(|c| </span><span style="color:#81a1c1;">*</span><span>c</span><span style="color:#81a1c1;">.</span><span>downcast_ref</span><span style="color:#81a1c1;">::</span><span><</span><span style="color:#81a1c1;">f64</span><span>>()</span><span style="color:#81a1c1;">.</span><span style="color:#88c0d0;">unwrap</span><span>())
</span><span> </span><span style="color:#81a1c1;">.</span><span>collect</span><span style="color:#81a1c1;">::</span><span><</span><span style="color:#8fbcbb;">Vec</span><span><</span><span style="color:#81a1c1;">f64</span><span>>>()</span><span style="color:#eceff4;">;
</span><span> </span><span style="color:#8fbcbb;">Ok</span><span>(())
</span><span>}
</span></code></pre>
<p>That looks simple if you were targeting GNOME Shell as the only desktop environment. Supporting other desktop environments like KDE or any X11 based one for example would require more code from the application developer side.</p>
<h1 id="introducing-portals">Introducing portals</h1>
<p>Portals as in XDG portals, are a bunch of DBus interfaces as a specification that a desktop environment can implement. An application developer can then communicate with the XDG portal DBus interface instead of the desktop environment specific one. The portal implementation will take care of calling the available backend. By their nature, the portals can't be tied to Flatpak except the few portals that were made especially for Flatpak'ed applications like the update monitor one.</p>
<p>You can find the list of the available portals by looking at the <a rel="nofollow noreferrer" href="https://flatpak.github.io/xdg-desktop-portal/index.html#idm28">specifications</a>. Let's try to reimplement the same thing using the <a rel="nofollow noreferrer" href="https://flatpak.github.io/xdg-desktop-portal/index.html#gdbus-method-org-freedesktop-portal-Screenshot.PickColor">XDG portal</a> instead of the GNOME Shell one. As portals requires a user interaction, most of the method calls in the various portals returns an <a rel="nofollow noreferrer" href="https://docs.rs/zvariant/2.1.0/zvariant/struct.ObjectPath.html">ObjectPath</a> like this one <code>/org/freedesktop/portal/desktop/request/SENDER/TOKEN</code> that represents a <a rel="nofollow noreferrer" href="https://flatpak.github.io/xdg-desktop-portal/index.html#gdbus-org.freedesktop.portal.Request">Request</a>. We should then listen to a <a rel="nofollow noreferrer" href="https://flatpak.github.io/xdg-desktop-portal/index.html#gdbus-signal-org-freedesktop-portal-Request.Response">Response</a> signal to get the result.</p>
<pre data-lang="rust" style="background-color:#2e3440;color:#d8dee9;" class="language-rust "><code class="language-rust" data-lang="rust"><span style="color:#81a1c1;">use </span><span>zvariant</span><span style="color:#81a1c1;">::</span><span>{OwnedObjectPath</span><span style="color:#eceff4;">,</span><span> OwnedValue}</span><span style="color:#eceff4;">;
</span><span style="color:#81a1c1;">use </span><span>zbus</span><span style="color:#81a1c1;">::</span><span>{dbus_proxy</span><span style="color:#eceff4;">, </span><span>fdo</span><span style="color:#81a1c1;">::</span><span>Result}</span><span style="color:#eceff4;">;
</span><span style="color:#81a1c1;">use </span><span>std</span><span style="color:#81a1c1;">::</span><span>collections</span><span style="color:#81a1c1;">::</span><span>HashMap</span><span style="color:#eceff4;">;
</span><span>
</span><span>#[dbus_proxy(
</span><span> interface </span><span style="color:#81a1c1;">= </span><span style="color:#a3be8c;">"org.freedesktop.portal.Screenshot"</span><span style="color:#eceff4;">,
</span><span> default_service </span><span style="color:#81a1c1;">= </span><span style="color:#a3be8c;">"org.freedesktop.portal.Desktop"</span><span style="color:#eceff4;">,
</span><span> default_path </span><span style="color:#81a1c1;">= </span><span style="color:#a3be8c;">"/org/freedesktop/portal/desktop"
</span><span>)]
</span><span style="color:#616e88;">/// The interface lets sandboxed applications request a screenshot.
</span><span style="color:#81a1c1;">trait </span><span style="color:#8fbcbb;">Screenshot </span><span>{
</span><span> </span><span style="color:#81a1c1;">fn </span><span style="color:#88c0d0;">pick_color</span><span>(
</span><span> </span><span style="color:#81a1c1;">&</span><span>self,
</span><span> parent_window</span><span style="color:#eceff4;">: </span><span style="color:#81a1c1;">&str</span><span>,
</span><span> options</span><span style="color:#eceff4;">: </span><span>HashMap<</span><span style="color:#8fbcbb;">String</span><span>, OwnedValue>,
</span><span> ) </span><span style="color:#eceff4;">-> </span><span style="color:#8fbcbb;">Result</span><span><OwnedObjectPath></span><span style="color:#eceff4;">;
</span><span>}
</span></code></pre>
<p>Calling the pick colour method will now gives as an Object Path instead of the result that would normally contain the colour.</p>
<pre data-lang="rust" style="background-color:#2e3440;color:#d8dee9;" class="language-rust "><code class="language-rust" data-lang="rust"><span style="color:#81a1c1;">let</span><span> connection </span><span style="color:#81a1c1;">= </span><span>zbus</span><span style="color:#81a1c1;">::</span><span>Connection</span><span style="color:#81a1c1;">::</span><span>new_session()</span><span style="color:#81a1c1;">?</span><span style="color:#eceff4;">;
</span><span style="color:#81a1c1;">let</span><span> proxy </span><span style="color:#81a1c1;">= </span><span>ScreenshotProxy</span><span style="color:#81a1c1;">::</span><span>new(</span><span style="color:#81a1c1;">&</span><span>connection)</span><span style="color:#81a1c1;">?</span><span style="color:#eceff4;">;
</span><span style="color:#616e88;">// We don't have a window identifier, see https://flatpak.github.io/xdg-desktop-portal/index.html#parent_window
</span><span style="color:#81a1c1;">let</span><span> request_handle </span><span style="color:#81a1c1;">=</span><span> proxy</span><span style="color:#81a1c1;">.</span><span style="color:#88c0d0;">pick_color</span><span>(</span><span style="color:#a3be8c;">""</span><span style="color:#eceff4;">, </span><span>HashMap</span><span style="color:#81a1c1;">::</span><span>new())</span><span style="color:#81a1c1;">?</span><span style="color:#eceff4;">;
</span><span>println!(</span><span style="color:#a3be8c;">"</span><span style="color:#ebcb8b;">{:#?}</span><span style="color:#a3be8c;">"</span><span style="color:#eceff4;">,</span><span> request_handle</span><span style="color:#81a1c1;">.</span><span style="color:#88c0d0;">as_str</span><span>())</span><span style="color:#eceff4;">;
</span></code></pre>
<p>Now that we have the necessary object path we can listen to a response signal. As of today, zbus doesn't provide a higher level API to await for a response signal. We will do with a simple loop as we can break out of it once we have received a Response signal.</p>
<pre data-lang="rust" style="background-color:#2e3440;color:#d8dee9;" class="language-rust "><code class="language-rust" data-lang="rust"><span style="color:#81a1c1;">loop </span><span>{
</span><span> </span><span style="color:#81a1c1;">let</span><span> msg </span><span style="color:#81a1c1;">=</span><span> connection</span><span style="color:#81a1c1;">.</span><span style="color:#88c0d0;">receive_message</span><span>()</span><span style="color:#81a1c1;">?</span><span style="color:#eceff4;">;
</span><span> </span><span style="color:#81a1c1;">let</span><span> msg_header </span><span style="color:#81a1c1;">=</span><span> msg</span><span style="color:#81a1c1;">.</span><span style="color:#88c0d0;">header</span><span>()</span><span style="color:#81a1c1;">?</span><span style="color:#eceff4;">;
</span><span> </span><span style="color:#81a1c1;">if</span><span> msg_header</span><span style="color:#81a1c1;">.</span><span style="color:#88c0d0;">message_type</span><span>()</span><span style="color:#81a1c1;">? == </span><span>zbus</span><span style="color:#81a1c1;">::</span><span>MessageType</span><span style="color:#81a1c1;">::</span><span>Signal
</span><span> </span><span style="color:#81a1c1;">&</span><span> msg_header</span><span style="color:#81a1c1;">.</span><span style="color:#88c0d0;">member</span><span>()</span><span style="color:#81a1c1;">? == </span><span style="color:#8fbcbb;">Some</span><span>(</span><span style="color:#a3be8c;">"Response"</span><span>)
</span><span> {
</span><span> </span><span style="color:#616e88;">// We can retrieve the body here, but we need to de-serialize it.
</span><span> </span><span style="color:#81a1c1;">let</span><span> response </span><span style="color:#81a1c1;">=</span><span> msg</span><span style="color:#81a1c1;">.</span><span>body</span><span style="color:#81a1c1;">::</span><span><T>()</span><span style="color:#81a1c1;">?</span><span style="color:#eceff4;">;
</span><span> }
</span><span>}
</span></code></pre>
<p>The type T here should implement <code>zvariant::Type</code> & <code>serde::de::DeserializeOwned</code>. From the <a rel="nofollow noreferrer" href="https://flatpak.github.io/xdg-desktop-portal/index.html#gdbus-signal-org-freedesktop-portal-Request.Response">signal documentation</a> we can figure out the struct we would need to de-serialize a typical response </p>
<pre data-lang="rust" style="background-color:#2e3440;color:#d8dee9;" class="language-rust "><code class="language-rust" data-lang="rust"><span style="color:#81a1c1;">use </span><span>serde_repr</span><span style="color:#81a1c1;">::</span><span>{Deserialize_repr</span><span style="color:#eceff4;">,</span><span> Serialize_repr}</span><span style="color:#eceff4;">;
</span><span style="color:#81a1c1;">use </span><span>serde</span><span style="color:#81a1c1;">::</span><span>{Serialize</span><span style="color:#eceff4;">,</span><span> Deserialize}</span><span style="color:#eceff4;">;
</span><span style="color:#81a1c1;">use </span><span>zvariant_derive</span><span style="color:#81a1c1;">::</span><span>Type</span><span style="color:#eceff4;">;
</span><span>
</span><span>#[derive(Serialize_repr</span><span style="color:#eceff4;">,</span><span> Deserialize_repr</span><span style="color:#eceff4;">,</span><span> PartialEq</span><span style="color:#eceff4;">,</span><span> Debug</span><span style="color:#eceff4;">,</span><span> Type)]
</span><span>#[repr(u32)]
</span><span style="color:#81a1c1;">enum </span><span style="color:#8fbcbb;">ResponseType </span><span>{
</span><span> </span><span style="color:#616e88;">/// Success, the request is carried out
</span><span> Success </span><span style="color:#81a1c1;">= </span><span style="color:#b48ead;">0</span><span style="color:#eceff4;">,
</span><span> </span><span style="color:#616e88;">/// The user cancelled the interaction
</span><span> Cancelled </span><span style="color:#81a1c1;">= </span><span style="color:#b48ead;">1</span><span style="color:#eceff4;">,
</span><span> </span><span style="color:#616e88;">/// The user interaction was ended in some other way
</span><span> Other </span><span style="color:#81a1c1;">= </span><span style="color:#b48ead;">2</span><span style="color:#eceff4;">,
</span><span>}
</span><span>
</span><span>#[derive(Serialize</span><span style="color:#eceff4;">,</span><span> Deserialize</span><span style="color:#eceff4;">,</span><span> Debug</span><span style="color:#eceff4;">,</span><span> Type)]
</span><span style="color:#81a1c1;">pub struct </span><span style="color:#8fbcbb;">Response</span><span>(ResponseType, HashMap<</span><span style="color:#8fbcbb;">String</span><span>, OwnedValue>)</span><span style="color:#eceff4;">;
</span></code></pre>
<p>We can now de-serialize the body into a Response and call a <code>FnOnce</code> on it. The HashMap in the case of a pick colour call will contain a single key <code>color</code> with a <code>[f64;3]</code> value. <a rel="nofollow noreferrer" href="https://docs.rs/zvariant_derive/2.1.0/zvariant_derive/">zvariant_derive</a> has a neat macro that allow us to de-serialize a <code>a{sv}</code> into a struct.</p>
<pre data-lang="rust" style="background-color:#2e3440;color:#d8dee9;" class="language-rust "><code class="language-rust" data-lang="rust"><span style="color:#81a1c1;">use </span><span>zvariant_derive</span><span style="color:#81a1c1;">::</span><span>{SerializeDict</span><span style="color:#eceff4;">,</span><span> DeserializeDict</span><span style="color:#eceff4;">,</span><span> TypeDict}</span><span style="color:#eceff4;">;
</span><span>
</span><span>#[derive(SerializeDict</span><span style="color:#eceff4;">,</span><span> DeserializeDict</span><span style="color:#eceff4;">,</span><span> Debug</span><span style="color:#eceff4;">,</span><span> TypeDict)]
</span><span style="color:#81a1c1;">struct </span><span style="color:#8fbcbb;">ColorResponse </span><span>{
</span><span> </span><span style="color:#81a1c1;">pub </span><span>color</span><span style="color:#eceff4;">:</span><span> [</span><span style="color:#81a1c1;">f64</span><span>; 3],
</span><span>}
</span><span>
</span><span>#[derive(Serialize</span><span style="color:#eceff4;">,</span><span> Deserialize</span><span style="color:#eceff4;">,</span><span> Debug</span><span style="color:#eceff4;">,</span><span> Type)]
</span><span style="color:#81a1c1;">pub struct </span><span style="color:#8fbcbb;">Response</span><span>(pub ResponseType, pub ColorResponse)</span><span style="color:#eceff4;">;
</span><span>
</span><span style="color:#81a1c1;">fn </span><span style="color:#88c0d0;">main</span><span>() </span><span style="color:#eceff4;">-> </span><span>zbus</span><span style="color:#81a1c1;">::</span><span>fdo</span><span style="color:#81a1c1;">::</span><span style="color:#8fbcbb;">Result</span><span><()> {
</span><span> </span><span style="color:#81a1c1;">let</span><span> connection </span><span style="color:#81a1c1;">= </span><span>zbus</span><span style="color:#81a1c1;">::</span><span>Connection</span><span style="color:#81a1c1;">::</span><span>new_session()</span><span style="color:#81a1c1;">?</span><span style="color:#eceff4;">;
</span><span> </span><span style="color:#81a1c1;">let</span><span> proxy </span><span style="color:#81a1c1;">= </span><span>ScreenshotProxy</span><span style="color:#81a1c1;">::</span><span>new(</span><span style="color:#81a1c1;">&</span><span>connection)</span><span style="color:#81a1c1;">?</span><span style="color:#eceff4;">;
</span><span>
</span><span> proxy</span><span style="color:#81a1c1;">.</span><span style="color:#88c0d0;">pick_color</span><span>(</span><span style="color:#a3be8c;">""</span><span style="color:#eceff4;">, </span><span>HashMap</span><span style="color:#81a1c1;">::</span><span>new())</span><span style="color:#81a1c1;">?</span><span style="color:#eceff4;">;
</span><span>
</span><span> </span><span style="color:#81a1c1;">let </span><span style="color:#88c0d0;">callback </span><span style="color:#81a1c1;">= </span><span>|r</span><span style="color:#eceff4;">:</span><span> Response| {
</span><span> </span><span style="color:#81a1c1;">if</span><span> r</span><span style="color:#81a1c1;">.</span><span style="color:#b48ead;">0 </span><span style="color:#81a1c1;">== </span><span>ResponseType</span><span style="color:#81a1c1;">::</span><span>Success {
</span><span> println!(</span><span style="color:#a3be8c;">"</span><span style="color:#ebcb8b;">{:#?}</span><span style="color:#a3be8c;">"</span><span style="color:#eceff4;">,</span><span> r</span><span style="color:#81a1c1;">.</span><span style="color:#b48ead;">1.</span><span>color)</span><span style="color:#eceff4;">;
</span><span> }
</span><span> }</span><span style="color:#eceff4;">;
</span><span> </span><span style="color:#81a1c1;">loop </span><span>{
</span><span> </span><span style="color:#81a1c1;">let</span><span> msg </span><span style="color:#81a1c1;">=</span><span> connection</span><span style="color:#81a1c1;">.</span><span style="color:#88c0d0;">receive_message</span><span>()</span><span style="color:#81a1c1;">?</span><span style="color:#eceff4;">;
</span><span> </span><span style="color:#81a1c1;">let</span><span> msg_header </span><span style="color:#81a1c1;">=</span><span> msg</span><span style="color:#81a1c1;">.</span><span style="color:#88c0d0;">header</span><span>()</span><span style="color:#81a1c1;">?</span><span style="color:#eceff4;">;
</span><span> </span><span style="color:#81a1c1;">if</span><span> msg_header</span><span style="color:#81a1c1;">.</span><span style="color:#88c0d0;">message_type</span><span>()</span><span style="color:#81a1c1;">? == </span><span>zbus</span><span style="color:#81a1c1;">::</span><span>MessageType</span><span style="color:#81a1c1;">::</span><span>Signal
</span><span> </span><span style="color:#81a1c1;">&</span><span> msg_header</span><span style="color:#81a1c1;">.</span><span style="color:#88c0d0;">member</span><span>()</span><span style="color:#81a1c1;">? == </span><span style="color:#8fbcbb;">Some</span><span>(</span><span style="color:#a3be8c;">"Response"</span><span>)
</span><span> {
</span><span> </span><span style="color:#81a1c1;">let</span><span> response </span><span style="color:#81a1c1;">=</span><span> msg</span><span style="color:#81a1c1;">.</span><span>body</span><span style="color:#81a1c1;">::</span><span><Response>()</span><span style="color:#81a1c1;">?</span><span style="color:#eceff4;">;
</span><span> </span><span style="color:#88c0d0;">callback</span><span>(response)</span><span style="color:#eceff4;">;
</span><span> </span><span style="color:#81a1c1;">break</span><span style="color:#eceff4;">;
</span><span> }
</span><span> }
</span><span> </span><span style="color:#8fbcbb;">Ok</span><span>(())
</span><span>}
</span></code></pre>
<p>Despite zbus being pretty straightforward to use, using portals requires the developer to look at the specifications and figure out which options a portal request can take or the possible responses that you might receive. Those were the primary reasons I wrote ASHPD.</p>
<h1 id="the-ashpd-crate">The ASHPD crate</h1>
<p>an acronym of Aperture Science Handheld Portal Device, which is the name of the portal gun in the Portal game, is a crate that aims to provide a simple API around the portals to consume from Rust. </p>
<p>Let's see how can we pick the colour now using ashpd</p>
<pre data-lang="rust" style="background-color:#2e3440;color:#d8dee9;" class="language-rust "><code class="language-rust" data-lang="rust"><span style="color:#81a1c1;">use </span><span>ashpd</span><span style="color:#81a1c1;">::</span><span>desktop</span><span style="color:#81a1c1;">::</span><span>screenshot</span><span style="color:#81a1c1;">::</span><span>{Color</span><span style="color:#eceff4;">,</span><span> PickColorOptions</span><span style="color:#eceff4;">,</span><span> ScreenshotProxy}</span><span style="color:#eceff4;">;
</span><span style="color:#81a1c1;">use </span><span>ashpd</span><span style="color:#81a1c1;">::</span><span>{RequestProxy</span><span style="color:#eceff4;">,</span><span> Response</span><span style="color:#eceff4;">,</span><span> WindowIdentifier}</span><span style="color:#eceff4;">;
</span><span style="color:#81a1c1;">use </span><span>zbus</span><span style="color:#81a1c1;">::</span><span>{</span><span style="color:#81a1c1;">self</span><span style="color:#eceff4;">, </span><span>fdo</span><span style="color:#81a1c1;">::</span><span>Result}</span><span style="color:#eceff4;">;
</span><span>
</span><span style="color:#81a1c1;">fn </span><span style="color:#88c0d0;">main</span><span>() </span><span style="color:#eceff4;">-> </span><span style="color:#8fbcbb;">Result</span><span><()> {
</span><span> </span><span style="color:#81a1c1;">let</span><span> connection </span><span style="color:#81a1c1;">= </span><span>zbus</span><span style="color:#81a1c1;">::</span><span>Connection</span><span style="color:#81a1c1;">::</span><span>new_session()</span><span style="color:#81a1c1;">?</span><span style="color:#eceff4;">;
</span><span> </span><span style="color:#81a1c1;">let</span><span> proxy </span><span style="color:#81a1c1;">= </span><span>ScreenshotProxy</span><span style="color:#81a1c1;">::</span><span>new(</span><span style="color:#81a1c1;">&</span><span>connection)</span><span style="color:#81a1c1;">?</span><span style="color:#eceff4;">;
</span><span>
</span><span> </span><span style="color:#81a1c1;">let</span><span> request_handle </span><span style="color:#81a1c1;">=</span><span> proxy</span><span style="color:#81a1c1;">.</span><span style="color:#88c0d0;">pick_color</span><span>(
</span><span> WindowIdentifier</span><span style="color:#81a1c1;">::</span><span>default()</span><span style="color:#eceff4;">,
</span><span> PickColorOptions</span><span style="color:#81a1c1;">::</span><span>default()
</span><span> )</span><span style="color:#81a1c1;">?</span><span style="color:#eceff4;">;
</span><span>
</span><span> </span><span style="color:#81a1c1;">let</span><span> request </span><span style="color:#81a1c1;">= </span><span>RequestProxy</span><span style="color:#81a1c1;">::</span><span>new(</span><span style="color:#81a1c1;">&</span><span>connection</span><span style="color:#eceff4;">, </span><span style="color:#81a1c1;">&</span><span>request_handle)</span><span style="color:#81a1c1;">?</span><span style="color:#eceff4;">;
</span><span> request</span><span style="color:#81a1c1;">.</span><span style="color:#88c0d0;">on_response</span><span>(|response</span><span style="color:#eceff4;">: </span><span>Response<Color>| {
</span><span> </span><span style="color:#81a1c1;">if let </span><span style="color:#8fbcbb;">Ok</span><span>(color) </span><span style="color:#81a1c1;">=</span><span> response {
</span><span> println!(</span><span style="color:#a3be8c;">"(</span><span style="color:#ebcb8b;">{}</span><span style="color:#a3be8c;">, </span><span style="color:#ebcb8b;">{}</span><span style="color:#a3be8c;">, </span><span style="color:#ebcb8b;">{}</span><span style="color:#a3be8c;">)"</span><span style="color:#eceff4;">,</span><span> color</span><span style="color:#81a1c1;">.</span><span style="color:#88c0d0;">red</span><span>()</span><span style="color:#eceff4;">,</span><span> color</span><span style="color:#81a1c1;">.</span><span style="color:#88c0d0;">green</span><span>()</span><span style="color:#eceff4;">,</span><span> color</span><span style="color:#81a1c1;">.</span><span style="color:#88c0d0;">blue</span><span>())</span><span style="color:#eceff4;">;
</span><span> }
</span><span> })</span><span style="color:#81a1c1;">?</span><span style="color:#eceff4;">;
</span><span>
</span><span> </span><span style="color:#8fbcbb;">Ok</span><span>(())
</span><span>}
</span></code></pre>
<p>The crate proves it's usefulness for more complex portals like the <a rel="nofollow noreferrer" href="https://flatpak.github.io/xdg-desktop-portal/index.html#gdbus-org.freedesktop.portal.FileChooser">file chooser</a>, here's an example from the docs on how easy it's to ask the user to select a file using the native file chooser without linking against GTK or Qt.</p>
<pre data-lang="rust" style="background-color:#2e3440;color:#d8dee9;" class="language-rust "><code class="language-rust" data-lang="rust"><span style="color:#81a1c1;">use </span><span>ashpd</span><span style="color:#81a1c1;">::</span><span>desktop</span><span style="color:#81a1c1;">::</span><span>file_chooser</span><span style="color:#81a1c1;">::</span><span>{
</span><span> Choice</span><span style="color:#eceff4;">,</span><span> FileChooserProxy</span><span style="color:#eceff4;">,</span><span> FileFilter</span><span style="color:#eceff4;">,</span><span> SelectedFiles</span><span style="color:#eceff4;">,</span><span> OpenFileOptions</span><span style="color:#eceff4;">,
</span><span>}</span><span style="color:#eceff4;">;
</span><span style="color:#81a1c1;">use </span><span>ashpd</span><span style="color:#81a1c1;">::</span><span>{RequestProxy</span><span style="color:#eceff4;">,</span><span> Response</span><span style="color:#eceff4;">,</span><span> WindowIdentifier}</span><span style="color:#eceff4;">;
</span><span style="color:#81a1c1;">use </span><span>zbus</span><span style="color:#81a1c1;">::</span><span>{fdo</span><span style="color:#81a1c1;">::</span><span>Result</span><span style="color:#eceff4;">,</span><span> Connection}</span><span style="color:#eceff4;">;
</span><span>
</span><span style="color:#81a1c1;">fn </span><span style="color:#88c0d0;">main</span><span>() </span><span style="color:#eceff4;">-> </span><span style="color:#8fbcbb;">Result</span><span><()> {
</span><span> </span><span style="color:#81a1c1;">let</span><span> connection </span><span style="color:#81a1c1;">= </span><span>Connection</span><span style="color:#81a1c1;">::</span><span>new_session()</span><span style="color:#81a1c1;">?</span><span style="color:#eceff4;">;
</span><span>
</span><span> </span><span style="color:#81a1c1;">let</span><span> proxy </span><span style="color:#81a1c1;">= </span><span>FileChooserProxy</span><span style="color:#81a1c1;">::</span><span>new(</span><span style="color:#81a1c1;">&</span><span>connection)</span><span style="color:#81a1c1;">?</span><span style="color:#eceff4;">;
</span><span> </span><span style="color:#81a1c1;">let</span><span> request_handle </span><span style="color:#81a1c1;">=</span><span> proxy</span><span style="color:#81a1c1;">.</span><span style="color:#88c0d0;">open_file</span><span>(
</span><span> WindowIdentifier</span><span style="color:#81a1c1;">::</span><span>default()</span><span style="color:#eceff4;">,
</span><span> </span><span style="color:#a3be8c;">"Select an SVG image to minify it"</span><span style="color:#eceff4;">,
</span><span> OpenFileOptions</span><span style="color:#81a1c1;">::</span><span>default()
</span><span> </span><span style="color:#81a1c1;">.</span><span style="color:#88c0d0;">accept_label</span><span>(</span><span style="color:#a3be8c;">"_Open File"</span><span>)
</span><span> </span><span style="color:#81a1c1;">.</span><span style="color:#88c0d0;">modal</span><span>(</span><span style="color:#81a1c1;">true</span><span>)
</span><span> </span><span style="color:#81a1c1;">.</span><span style="color:#88c0d0;">multiple</span><span>(</span><span style="color:#81a1c1;">true</span><span>)
</span><span> </span><span style="color:#81a1c1;">.</span><span style="color:#88c0d0;">filter</span><span>(FileFilter</span><span style="color:#81a1c1;">::</span><span>new(</span><span style="color:#a3be8c;">"SVG Image"</span><span>)</span><span style="color:#81a1c1;">.</span><span style="color:#88c0d0;">mimetype</span><span>(</span><span style="color:#a3be8c;">"image/svg+xml"</span><span>))</span><span style="color:#eceff4;">,
</span><span> )</span><span style="color:#81a1c1;">?</span><span style="color:#eceff4;">;
</span><span>
</span><span> </span><span style="color:#81a1c1;">let</span><span> request </span><span style="color:#81a1c1;">= </span><span>RequestProxy</span><span style="color:#81a1c1;">::</span><span>new(</span><span style="color:#81a1c1;">&</span><span>connection</span><span style="color:#eceff4;">, </span><span style="color:#81a1c1;">&</span><span>request_handle)</span><span style="color:#81a1c1;">?</span><span style="color:#eceff4;">;
</span><span> request</span><span style="color:#81a1c1;">.</span><span style="color:#88c0d0;">on_response</span><span>(|r</span><span style="color:#eceff4;">: </span><span>Response<SelectedFiles>| {
</span><span> println!(</span><span style="color:#a3be8c;">"</span><span style="color:#ebcb8b;">{:#?}</span><span style="color:#a3be8c;">"</span><span style="color:#eceff4;">,</span><span> r</span><span style="color:#81a1c1;">.</span><span style="color:#88c0d0;">unwrap</span><span>())</span><span style="color:#eceff4;">;
</span><span> })</span><span style="color:#81a1c1;">?</span><span style="color:#eceff4;">;
</span><span>
</span><span> </span><span style="color:#8fbcbb;">Ok</span><span>(())
</span><span>}
</span></code></pre>
<p>Currently the crate supports all the available portals, though some signals might be missing till a proper signal support lands in upstream zbus. I'm very thankful for the help/support I've got from Zeeshan & Marc-André Lureau, my work wouldn't have been possible without zbus & zvariant.</p>
<p>If you would like to contribute, read a bit how some of the portals work or just have a more complex DBus API wrapper to learn how to use zbus</p>
<ul>
<li>Source code: <a rel="nofollow noreferrer" href="https://github.com/bilelmoussaoui/ashpd">https://github.com/bilelmoussaoui/ashpd</a></li>
<li>Crates.io: <a rel="nofollow noreferrer" href="https://crates.io/crates/ashpd">https://crates.io/crates/ashpd</a></li>
<li>Documentation : <a rel="nofollow noreferrer" href="https://docs.rs/ashpd/">https://docs.rs/ashpd/</a></li>
</ul>
<p>Until then, happy hacking!</p>
libhandy-rs v0.6.0 is out!2020-07-09T00:00:00+00:002020-07-09T00:00:00+00:00
Unknown
https://belmoussaoui.com/blog/11-libhandy-rs-0-6-0/<p>Recently I kind of took over the maintainership of <a rel="nofollow noreferrer" href="https://gitlab.gnome.org/World/Rust/libhandy-rs">libhandy-rs</a>, the Rust bindings of <a rel="nofollow noreferrer" href="https://gitlab.gnome.org/gnome/libhandy">libhandy</a>. I have since then been preparing for a new release so that Rust & GTK app developers can update to the <a rel="nofollow noreferrer" href="https://gtk-rs.org/blog/2020/07/04/new-release.html">latest gtk-rs release</a> as soon as possible. I also heavily depend on it on my various little apps.</p>
<p>The latest release which targets libhandy-0.0 v0.0.13 features various improvements:</p>
<ul>
<li>
<p>Proper <a rel="nofollow noreferrer" href="https://world.pages.gitlab.gnome.org/Rust/libhandy-rs/libhandy/">documentation</a> support, note that the docs are built from the main branch</p>
</li>
<li>
<p>Builders support</p>
</li>
<li>
<p>Generate more missing bindings</p>
</li>
<li>
<p>Starting from this release, libhandy-rs have a <code>libhandy::init()</code> which should be called right after <code>gtk::init()</code></p>
</li>
</ul>
<p>The next release of libhandy-rs will be targeting libhandy-1 and will hopefully adds the latest missing bits for a complete Rust bindings of libhandy.</p>
<p>A big thanks to the GTK-rs community, I learned so much stuff by contributing :)</p>
New Website!2020-02-28T00:00:00+00:002020-02-28T00:00:00+00:00
Unknown
https://belmoussaoui.com/blog/10-new-website/<p>I have been thinking of writing a WordPress theme for the blog I had and I noticed that there were too many extensions, too many externally loaded files and trying to fix all of that would have taken me forever. I decided to take that as an opportunity to create something simple and nice with Django & React. </p>
<h2 id="the-backend">The Backend</h2>
<p>Built with <a rel="nofollow noreferrer" href="https://www.django-rest-framework.org/">Django REST Framework</a> which makes providing a set of API endpoints pretty straightforward. These endpoints are mostly to fetch data from a PostgreSQL database or add/delete entries if a token is provided. The authentication is handled by <a rel="nofollow noreferrer" href="https://james1345.github.io/django-rest-knox/">Knox</a> & is used to provide a simple dashboard so I can manage the content I have on my blog.</p>
<h2 id="the-frontend">The Frontend</h2>
<p>From configuring Webpack to produce what I want to write CSS, it was the most challenging part of the project. I ended up using <a rel="nofollow noreferrer" href="https://bulma.io/">Bulma</a> as a CSS framework which worked out pretty nicely! One thing I wanted to make sure of is to have the smallest bundle possible and avoid loading any external resources. I have replaced the externally loaded Google fonts with the <a rel="nofollow noreferrer" href="https://rsms.me/inter/">Inter Typeface</a> family ❤️ and distributed the JS into smaller chunks that are loaded only when used, bundled the few SVG icons I used instead of FontAwesome, removed the unused locales of moments.js and uglified the exported files. </p>
<p>I will try to write soon about the technical challenges I had to deal with and hopefully, the new blog will help with that!</p>
<p>Until then, enjoy the new spyware-less blog and stay safe :)</p>
Tick Tock Clocks got redesigned!2020-02-23T00:00:00+00:002020-02-23T00:00:00+00:00
Unknown
https://belmoussaoui.com/blog/9-tick-tock-clocks-got-redesigned/<p>Few months back, I convinced Zander Brown to take over GNOME Clocks with me and we have been working hard to refresh the code base and give it a new look for GNOME 3.36.</p>
<p>So far, we have got all the four panels re-designed based on the mockups made by the GNOME design team.</p>
<p><img src="/posts/9-tick-tock-clocks-got-redesigned/alarms-1.png" alt="Alarms" /></p>
<p><img src="/posts/9-tick-tock-clocks-got-redesigned/alarm-setup.png" alt="Alarms Setup" /></p>
<p><img src="/posts/9-tick-tock-clocks-got-redesigned/stopwatch-1.png" alt="Stopwatch" /></p>
<p><img src="/posts/9-tick-tock-clocks-got-redesigned/timer.png" alt="Timer" /></p>
<p><img src="/posts/9-tick-tock-clocks-got-redesigned/world.png" alt="World" /></p>
<p>Of course, one major thing about the new design is that the application is fully adaptive now.</p>
<p><img src="/posts/9-tick-tock-clocks-got-redesigned/alarms-adaptive.png" alt="Alarms Adaptive" /></p>
<p><img src="/posts/9-tick-tock-clocks-got-redesigned/stopwatch-adaptive.png" alt="Stopwatch Adaptive" /></p>
<p><img src="/posts/9-tick-tock-clocks-got-redesigned/timer-adaptive.png" alt="Timer Adaptive" /></p>
<p><img src="/posts/9-tick-tock-clocks-got-redesigned/world-adaptive.png" alt="World Adaptive" /></p>
<p>The rest of the changes were basically code cleanup. It’s very satisfying to drop 500+ lines of dead code.</p>
<p>We have also added GNOME Clocks to the <a rel="nofollow noreferrer" href="https://wiki.gnome.org/Newcomers/ChooseProject">newcomers projects</a>, you should be able to pick up Clocks from <a rel="nofollow noreferrer" href="https://flathub.org/apps/details/org.gnome.Builder">GNOME Builder</a> which will hopefully help us attract more contributors. Please reach us at #clocks:gnome.org on Matrix if you would like to get involved.</p>
<p>The re-design is on master now, if you have some time to give it a try and report back that would be awesome! It should hit gnome-nightly pretty soon!</p>
<p>PS: sorry for the last minute translation work</p>
How to Flatpak a Rust application2020-02-08T00:00:00+00:002020-02-08T00:00:00+00:00
Unknown
https://belmoussaoui.com/blog/8-how-to-flatpak-a-rust-application/<p>Distributing and packaging your Rust GUI application and making it available for Linux users can be hard, I will try to explain the various ways of doing that using Flatpak as a packaging format.</p>
<p>I will be using a <a rel="nofollow noreferrer" href="http://gtk-rs.org/">GTK + Rust</a> application as an example to distribute it as a Flatpak in two different ways.</p>
<ul>
<li>
<p>The first one, involves adding a build system, on top of Cargo to handle installing the different files we will need to ensure our application can be found, installed and used by the users. We will be using <a rel="nofollow noreferrer" href="http://mesonbuild.com/">Meson</a></p>
</li>
<li>
<p>The second one, will just use Cargo and install those files manually during the Flatpak build</p>
</li>
</ul>
<h2 id="creating-a-simple-application">Creating a simple application</h2>
<p>Let’s start with creating a simple Rust project by typing</p>
<pre data-lang="bash" style="background-color:#2e3440;color:#d8dee9;" class="language-bash "><code class="language-bash" data-lang="bash"><span style="color:#88c0d0;">cargo</span><span> new rust-flatpak
</span></code></pre>
<p>The first step would be adding our dependencies to <code>Cargo.toml</code></p>
<pre data-lang="toml" style="background-color:#2e3440;color:#d8dee9;" class="language-toml "><code class="language-toml" data-lang="toml"><span>[package]
</span><span style="color:#81a1c1;">name </span><span>= </span><span style="color:#a3be8c;">"rust-flatpak"
</span><span style="color:#81a1c1;">version </span><span>= </span><span style="color:#a3be8c;">"0.1.0"
</span><span style="color:#81a1c1;">authors </span><span>= [</span><span style="color:#a3be8c;">"Bilal Elmoussaoui <bil.elmoussaoui@gmail.com>"</span><span>]
</span><span style="color:#81a1c1;">edition </span><span>= </span><span style="color:#a3be8c;">"2018"
</span><span>
</span><span style="color:#616e88;"># See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
</span><span>
</span><span>[dependencies]
</span><span style="color:#81a1c1;">gtk </span><span>= </span><span style="color:#a3be8c;">"0.8"
</span><span style="color:#81a1c1;">gio </span><span>= </span><span style="color:#a3be8c;">"0.8"
</span></code></pre>
<p>and create a simple window, the code is from <a rel="nofollow noreferrer" href="https://github.com/gtk-rs/examples/">https://github.com/gtk-rs/examples/</a>. You can check that repository if you need more examples.</p>
<pre data-lang="rust" style="background-color:#2e3440;color:#d8dee9;" class="language-rust "><code class="language-rust" data-lang="rust"><span style="color:#81a1c1;">use </span><span>gio</span><span style="color:#81a1c1;">::</span><span>prelude</span><span style="color:#81a1c1;">::*</span><span style="color:#eceff4;">;
</span><span style="color:#81a1c1;">use </span><span>gtk</span><span style="color:#81a1c1;">::</span><span>prelude</span><span style="color:#81a1c1;">::*</span><span style="color:#eceff4;">;
</span><span style="color:#81a1c1;">use </span><span>std</span><span style="color:#81a1c1;">::</span><span>env</span><span style="color:#81a1c1;">::</span><span>args</span><span style="color:#eceff4;">;
</span><span>
</span><span style="color:#81a1c1;">fn </span><span style="color:#88c0d0;">build_ui</span><span>(application</span><span style="color:#eceff4;">: </span><span style="color:#81a1c1;">&</span><span>amp;gtk</span><span style="color:#81a1c1;">::</span><span>Application) {
</span><span> </span><span style="color:#81a1c1;">let</span><span> window </span><span style="color:#81a1c1;">= </span><span>gtk</span><span style="color:#81a1c1;">::</span><span>ApplicationWindow</span><span style="color:#81a1c1;">::</span><span>new(application)</span><span style="color:#eceff4;">;
</span><span>
</span><span> window</span><span style="color:#81a1c1;">.</span><span style="color:#88c0d0;">set_title</span><span>(</span><span style="color:#a3be8c;">"First GTK+ Program"</span><span>)</span><span style="color:#eceff4;">;
</span><span> window</span><span style="color:#81a1c1;">.</span><span style="color:#88c0d0;">set_border_width</span><span>(</span><span style="color:#b48ead;">10</span><span>)</span><span style="color:#eceff4;">;
</span><span> window</span><span style="color:#81a1c1;">.</span><span style="color:#88c0d0;">set_position</span><span>(gtk</span><span style="color:#81a1c1;">::</span><span>WindowPosition</span><span style="color:#81a1c1;">::</span><span>Center)</span><span style="color:#eceff4;">;
</span><span> window</span><span style="color:#81a1c1;">.</span><span style="color:#88c0d0;">set_default_size</span><span>(</span><span style="color:#b48ead;">350</span><span style="color:#eceff4;">, </span><span style="color:#b48ead;">70</span><span>)</span><span style="color:#eceff4;">;
</span><span>
</span><span> </span><span style="color:#81a1c1;">let</span><span> button </span><span style="color:#81a1c1;">= </span><span>gtk</span><span style="color:#81a1c1;">::</span><span>Button</span><span style="color:#81a1c1;">::</span><span>new_with_label(</span><span style="color:#a3be8c;">"Click me!"</span><span>)</span><span style="color:#eceff4;">;
</span><span>
</span><span> window</span><span style="color:#81a1c1;">.</span><span style="color:#88c0d0;">add</span><span>(</span><span style="color:#81a1c1;">&</span><span>amp</span><span style="color:#eceff4;">;</span><span>button)</span><span style="color:#eceff4;">;
</span><span>
</span><span> window</span><span style="color:#81a1c1;">.</span><span style="color:#88c0d0;">show_all</span><span>()</span><span style="color:#eceff4;">;
</span><span>}
</span><span>
</span><span style="color:#81a1c1;">fn </span><span style="color:#88c0d0;">main</span><span>() {
</span><span> </span><span style="color:#81a1c1;">let</span><span> application </span><span style="color:#81a1c1;">=
</span><span> gtk</span><span style="color:#81a1c1;">::</span><span>Application</span><span style="color:#81a1c1;">::</span><span>new(</span><span style="color:#8fbcbb;">Some</span><span>(</span><span style="color:#a3be8c;">"com.belmoussaoui.RustFlatpak"</span><span>)</span><span style="color:#eceff4;">, </span><span style="color:#8fbcbb;">Default</span><span style="color:#81a1c1;">::</span><span>default())
</span><span> </span><span style="color:#81a1c1;">.</span><span style="color:#88c0d0;">expect</span><span>(</span><span style="color:#a3be8c;">"Initialization failed..."</span><span>)</span><span style="color:#eceff4;">;
</span><span>
</span><span> application</span><span style="color:#81a1c1;">.</span><span style="color:#88c0d0;">connect_activate</span><span>(|app| {
</span><span> </span><span style="color:#88c0d0;">build_ui</span><span>(app)</span><span style="color:#eceff4;">;
</span><span> })</span><span style="color:#eceff4;">;
</span><span>
</span><span> application</span><span style="color:#81a1c1;">.</span><span style="color:#88c0d0;">run</span><span>(</span><span style="color:#81a1c1;">&</span><span>amp</span><span style="color:#eceff4;">;</span><span style="color:#88c0d0;">args</span><span>()</span><span style="color:#81a1c1;">.</span><span>collect</span><span style="color:#81a1c1;">::</span><span><</span><span style="color:#8fbcbb;">Vec</span><span><</span><span style="color:#81a1c1;">_</span><span>>>())</span><span style="color:#eceff4;">;
</span><span>}
</span></code></pre>
<p>One important thing about how we create our a <code>gtk::Application</code> is the Application identifier we pass to it. If you don’t have an idea what an application id is, this guide should be complete <a rel="nofollow noreferrer" href="https://developer.gnome.org/ChooseApplicationID/">https://developer.gnome.org/ChooseApplicationID/</a>. Once we have finished building our application, the next step would be the packaging and distribution of what we just created.</p>
<p>We will need to prepare few files before doing so:</p>
<ul>
<li>
<p>A metainfo, which is a a file that describes your application for stores like Flathub, GNOME Software, KDE Discover or elementary’s AppCenter. The specifications of this file can be found at <a rel="nofollow noreferrer" href="https://www.freedesktop.org/software/appstream/docs/">https://www.freedesktop.org/software/appstream/docs/</a>.</p>
</li>
<li>
<p>A desktop file, which is the launcher of the application that the user finds on their app launcher thing/dashboard. It specifies the app name, the binary that should be executed, an icon name and probably other stuff likes categories/keywords. You can read more about that here <a rel="nofollow noreferrer" href="https://specifications.freedesktop.org/desktop-entry-spec/latest/">https://specifications.freedesktop.org/desktop-entry-spec/latest/</a></p>
</li>
<li>
<p>An icon, to make your app easier to find 🙂</p>
</li>
</ul>
<h2 id="packaging-related-files">Packaging related files</h2>
<h3 id="metainfo-file">Metainfo file</h3>
<p>Metainfo file, should include as much information about the application that allows the user to find it easily. You should provide some decent screenshots of the application, release tags and OARS tag. Providing a URL to your website or where to translate/report issues/donate could be added too.</p>
<p>The specs contains enough information about that, but here’s an example of such file, it should be named following <code>$app-id.metainfo.xml</code> and installed under <code>$prefix/$datadir/metainfo</code>.</p>
<pre data-lang="xml" style="background-color:#2e3440;color:#d8dee9;" class="language-xml "><code class="language-xml" data-lang="xml"><span style="color:#5e81ac;"><?xml </span><span style="color:#8fbcbb;">version</span><span style="color:#eceff4;">=</span><span style="color:#a3be8c;">"1.0" </span><span style="color:#8fbcbb;">encoding</span><span style="color:#eceff4;">=</span><span style="color:#a3be8c;">"UTF-8"</span><span style="color:#5e81ac;">?>
</span><span style="color:#616e88;"><!-- Bilal Elmoussaoui 2020 <bilal.elmoussaoui@gnome.org> -->
</span><span style="color:#81a1c1;"><component </span><span style="color:#8fbcbb;">type</span><span style="color:#eceff4;">=</span><span style="color:#a3be8c;">"desktop-application"</span><span style="color:#81a1c1;">>
</span><span style="color:#eceff4;"> </span><span style="color:#81a1c1;"><id></span><span style="color:#eceff4;">com.belmoussaoui.RustFlatpak</span><span style="color:#81a1c1;"></id>
</span><span style="color:#eceff4;"> </span><span style="color:#81a1c1;"><metadata_license></span><span style="color:#eceff4;">CC0</span><span style="color:#81a1c1;"></metadata_license>
</span><span style="color:#eceff4;"> </span><span style="color:#81a1c1;"><project_license></span><span style="color:#eceff4;">GPL-3.0+</span><span style="color:#81a1c1;"></project_license>
</span><span style="color:#eceff4;"> </span><span style="color:#81a1c1;"><name></span><span style="color:#eceff4;">Rust Flatpak</span><span style="color:#81a1c1;"></name>
</span><span style="color:#eceff4;"> </span><span style="color:#81a1c1;"><summary></span><span style="color:#eceff4;">Rust Flatpak-ed app?</span><span style="color:#81a1c1;"></summary>
</span><span style="color:#eceff4;"> </span><span style="color:#81a1c1;"><description>
</span><span style="color:#eceff4;"> </span><span style="color:#81a1c1;"><p></span><span style="color:#eceff4;">This metainfo is part of a Rust Flatpak-ed application</span><span style="color:#81a1c1;"></p>
</span><span style="color:#eceff4;"> </span><span style="color:#81a1c1;"></description>
</span><span style="color:#eceff4;"> </span><span style="color:#81a1c1;"><screenshots>
</span><span style="color:#eceff4;"> </span><span style="color:#81a1c1;"><screenshot </span><span style="color:#8fbcbb;">type</span><span style="color:#eceff4;">=</span><span style="color:#a3be8c;">"default"</span><span style="color:#81a1c1;">>
</span><span style="color:#eceff4;"> </span><span style="color:#81a1c1;"><image></span><span style="color:#eceff4;">https://gitlab.gnome.org/World/design/contrast/raw/master/data/resources/screenshots/screenshot1.png</span><span style="color:#81a1c1;"></image>
</span><span style="color:#eceff4;"> </span><span style="color:#81a1c1;"><caption></span><span style="color:#eceff4;">Main Window</span><span style="color:#81a1c1;"></caption>
</span><span style="color:#eceff4;"> </span><span style="color:#81a1c1;"></screenshot>
</span><span style="color:#eceff4;"> </span><span style="color:#81a1c1;"></screenshots>
</span><span style="color:#eceff4;"> </span><span style="color:#81a1c1;"><url </span><span style="color:#8fbcbb;">type</span><span style="color:#eceff4;">=</span><span style="color:#a3be8c;">"homepage"</span><span style="color:#81a1c1;">></span><span style="color:#eceff4;">https://belmoussaoui.com/</span><span style="color:#81a1c1;"></url>
</span><span style="color:#616e88;"><!--
</span><span style="color:#616e88;">Open Age Rating Service
</span><span style="color:#616e88;">https://hughsie.github.io/oars/index.html
</span><span style="color:#616e88;">-->
</span><span style="color:#eceff4;"> </span><span style="color:#81a1c1;"><content_rating </span><span style="color:#8fbcbb;">type</span><span style="color:#eceff4;">=</span><span style="color:#a3be8c;">"oars-1.0" </span><span style="color:#81a1c1;">/>
</span><span style="color:#eceff4;"> </span><span style="color:#81a1c1;"><releases>
</span><span style="color:#eceff4;"> </span><span style="color:#81a1c1;"><release </span><span style="color:#8fbcbb;">version</span><span style="color:#eceff4;">=</span><span style="color:#a3be8c;">"0.0.1" </span><span style="color:#8fbcbb;">date</span><span style="color:#eceff4;">=</span><span style="color:#a3be8c;">"2020-02-08"</span><span style="color:#81a1c1;">>
</span><span style="color:#eceff4;"> </span><span style="color:#81a1c1;"><description>
</span><span style="color:#eceff4;"> </span><span style="color:#81a1c1;"><p></span><span style="color:#eceff4;">First release of Rust Flatpak App</span><span style="color:#81a1c1;"></p>
</span><span style="color:#eceff4;"> </span><span style="color:#81a1c1;"></description>
</span><span style="color:#eceff4;"> </span><span style="color:#81a1c1;"></release>
</span><span style="color:#eceff4;"> </span><span style="color:#81a1c1;"></releases>
</span><span style="color:#eceff4;"> </span><span style="color:#81a1c1;"><kudos>
</span><span style="color:#eceff4;"> </span><span style="color:#616e88;"><!--
</span><span style="color:#616e88;"> GNOME Software kudos:
</span><span style="color:#616e88;"> https://gitlab.gnome.org/GNOME/gnome-software/blob/master/doc/kudos.md
</span><span style="color:#616e88;"> -->
</span><span style="color:#eceff4;"> </span><span style="color:#81a1c1;"><kudo></span><span style="color:#eceff4;">ModernToolkit</span><span style="color:#81a1c1;"></kudo>
</span><span style="color:#eceff4;"> </span><span style="color:#81a1c1;"><kudo></span><span style="color:#eceff4;">HiDpiIcon</span><span style="color:#81a1c1;"></kudo>
</span><span style="color:#eceff4;"> </span><span style="color:#81a1c1;"></kudos>
</span><span style="color:#eceff4;"> </span><span style="color:#81a1c1;"><developer_name></span><span style="color:#eceff4;">Bilal Elmoussaoui</span><span style="color:#81a1c1;"></developer_name>
</span><span style="color:#eceff4;"> </span><span style="color:#81a1c1;"><update_contact></span><span style="color:#eceff4;">bilal.elmoussaoui@gnome.org</span><span style="color:#81a1c1;"></update_contact>
</span><span style="color:#eceff4;">
</span><span style="color:#eceff4;"> </span><span style="color:#81a1c1;"><launchable </span><span style="color:#8fbcbb;">type</span><span style="color:#eceff4;">=</span><span style="color:#a3be8c;">"desktop-id"</span><span style="color:#81a1c1;">></span><span style="color:#eceff4;">com.belmoussaoui.RustFlatpak.desktop</span><span style="color:#81a1c1;"></launchable>
</span><span style="color:#81a1c1;"></component>
</span></code></pre>
<h3 id="desktop-file">Desktop file</h3>
<p>A desktop file is a simple launcher for the application, it shouldn’t contain more than. The file should be named <code>com.belmoussaoui.RustFlatpak.desktop</code>, if you look at the bottom of the metainfo file, we link the desktop file to it using the launchable tag. This allows the software center to figure out how to start your application.</p>
<p>The file should be installed under <code>$prefix/$datadir/applications</code></p>
<pre data-lang="ini" style="background-color:#2e3440;color:#d8dee9;" class="language-ini "><code class="language-ini" data-lang="ini"><span style="color:#81a1c1;">[Desktop Entry]
</span><span>Name</span><span style="color:#81a1c1;">=</span><span>Rust Flatpak
</span><span>Comment</span><span style="color:#81a1c1;">=</span><span>Rust Flapaked application
</span><span>Type</span><span style="color:#81a1c1;">=</span><span>Application
</span><span style="color:#616e88;"># should be the same as the Cargo project name
</span><span>Exec</span><span style="color:#81a1c1;">=</span><span>rust</span><span style="color:#81a1c1;">-</span><span>flatpak
</span><span>Terminal</span><span style="color:#81a1c1;">=false
</span><span>Categories</span><span style="color:#81a1c1;">=</span><span>Utility;</span><span style="color:#8fbcbb;">GTK</span><span>;
</span><span>Keywords</span><span style="color:#81a1c1;">=</span><span>Rust;Flatpak;</span><span style="color:#8fbcbb;">GTK</span><span>;
</span><span>Icon</span><span style="color:#81a1c1;">=</span><span>com</span><span style="color:#81a1c1;">.</span><span>belmoussaoui</span><span style="color:#81a1c1;">.</span><span>RustFlatpak
</span><span>StartupNotify</span><span style="color:#81a1c1;">=true
</span></code></pre>
<h3 id="icon">Icon</h3>
<p>The application should provide an icon, either in various sizes or as a scalable SVG version. Most stores uses either 64px or 128px icons, but the application icon might be used at smaller sizes like 48px somewhere else.</p>
<p>The icon should be installed under <code>$prefix/$datadir/icons/hicolor/scalable/apps/</code> in case you’re shipping an SVG icon. Otherwise it should be installed under <code>$prefix/$datadir/icons/hicolor/48x48/apps/</code>, the default available sizes are: 16, 32, 48, 64, 128, 256</p>
<h2 id="flatpak">Flatpak!</h2>
<h3 id="using-meson-build-system">Using Meson Build System</h3>
<p>The usage of Meson might seem not very useful if you’re not used to the GNOME Stack, but it provides a ton of features in a human readable way of writing things. It makes it pretty easy to use gettext for translating the desktop & metainfo files for examples, installing the icons at right place and so on. Features that Cargo can’t provide, at least, not in a simple way.</p>
<p>First step would be adding a <code>meson.build</code> files at the the same level as our main <code>Cargo.toml</code> in case your application is built split into different workspaces.</p>
<pre data-lang="meson" style="background-color:#2e3440;color:#d8dee9;" class="language-meson "><code class="language-meson" data-lang="meson"><span>project('rust-flatpak',
</span><span> 'rust',
</span><span> version: '0.1.0')
</span></code></pre>
<p>We started by defining our dependencies here, it might seems repetitive to have that here even if we do specify that in <code>Cargo.toml</code>, but it’s different. GTK is a a C library, and GTK Rust bindings just links to the library while providing a nice Rust wrapper around the C one. So you can’t build the application if those libraries can’t be detected by Cargo. Meson allows you to check that they are actually available before starting the build.</p>
<pre data-lang="meson" style="background-color:#2e3440;color:#d8dee9;" class="language-meson "><code class="language-meson" data-lang="meson"><span>dependency('glib-2.0')
</span><span>dependency('gio-2.0')
</span><span>dependency('gtk+-3.0')
</span></code></pre>
<p>Let’s create a <code>data</code> directory and put our desktop, metainfo files and icons on it. You can name it differently, it’s just a convention.</p>
<p>We need to tell Meson to check the build receipt we will have in <code>src</code> and <code>data</code></p>
<pre data-lang="meson" style="background-color:#2e3440;color:#d8dee9;" class="language-meson "><code class="language-meson" data-lang="meson"><span>subdir('data')
</span><span>subdir('src')
</span></code></pre>
<figure>
<img src="/posts/8-how-to-flatpak-a-rust-application/Screenshot-from-2020-02-08-21-14-06.png" >
<figcaption>Current files structure</figcaption>
</figure>
<p>Let’s start by telling Meson how to install the files under <code>data</code></p>
<pre data-lang="meson" style="background-color:#2e3440;color:#d8dee9;" class="language-meson "><code class="language-meson" data-lang="meson"><span>datadir = get_option('prefix') / get_option('datadir')
</span><span>
</span><span>application_id = 'com.belmoussaoui.RustFlatpak'
</span><span># Read more <https://mesonbuild.com/Reference-manual.html#install_data>
</span><span>
</span><span>install_data(
</span><span> '@0@.desktop'.format(application_id),
</span><span> install_dir: datadir / 'applications'
</span><span>)
</span><span>
</span><span>install_data(
</span><span> '@0@.metainfo.xml'.format(application_id),
</span><span> install_dir: datadir / 'metainfo'
</span><span>)
</span><span>
</span><span>install_data(
</span><span> '@0@.svg'.format(application_id),
</span><span> install_dir: datadir / 'icons' / 'hicolor' / 'scalable' / 'apps'
</span><span>)
</span></code></pre>
<p>You can notice that we are using <code>get_option</code> to get the prefix and datadir which could be defined at build time by passing <code>--prefix=/usr --datadir=share</code>.</p>
<p>Now, to the most complicated part of this, making Cargo & Meson happy friends. We can easily call Cargo from Meson and build our application, but we would like to define <code>$CARGO_HOME</code> during build, so we won’t have to re-download everything every time we rebuild the Flatpak. Sadly, ninja, “the build backend” used by Meson doesn’t support setting an env variable for a custom target. Until that’s fixed in both, we need to call a bash script from Meson that does call Cargo for us.</p>
<pre data-lang="meson" style="background-color:#2e3440;color:#d8dee9;" class="language-meson "><code class="language-meson" data-lang="meson"><span>sources = [
</span><span> 'main.rs',
</span><span>]
</span><span>
</span><span>cargo_script = find_program(join_paths(meson.source_root(), 'build-aux/cargo.sh'))
</span><span>cargo_release = custom_target(
</span><span> 'cargo-build',
</span><span> build_by_default: true,
</span><span> input: sources,
</span><span> output: meson.project_name(),
</span><span> console: true,
</span><span> install: true,
</span><span> install_dir: get_option('bindir'),
</span><span> command: [
</span><span> cargo_script,
</span><span> meson.build_root(),
</span><span> meson.source_root(),
</span><span> '@OUTPUT@',
</span><span> get_option('buildtype'),
</span><span> meson.project_name(),
</span><span> ]
</span><span>)
</span></code></pre>
<p>And by convention I put the script to call under <code>build-aux/cargo.sh</code></p>
<p>The content of the file should be</p>
<pre data-lang="bash" style="background-color:#2e3440;color:#d8dee9;" class="language-bash "><code class="language-bash" data-lang="bash"><span style="color:#616e88;">#!/bin/sh
</span><span>
</span><span style="color:#81a1c1;">export </span><span>MESON_BUILD_ROOT</span><span style="color:#81a1c1;">=</span><span style="color:#a3be8c;">"</span><span style="color:#81a1c1;">$</span><span>1</span><span style="color:#a3be8c;">"
</span><span style="color:#81a1c1;">export </span><span>MESON_SOURCE_ROOT</span><span style="color:#81a1c1;">=</span><span style="color:#a3be8c;">"</span><span style="color:#81a1c1;">$</span><span>2</span><span style="color:#a3be8c;">"
</span><span style="color:#81a1c1;">export </span><span>CARGO_TARGET_DIR</span><span style="color:#81a1c1;">=</span><span style="color:#a3be8c;">"</span><span style="color:#81a1c1;">$</span><span>MESON_BUILD_ROOT</span><span style="color:#a3be8c;">"/target
</span><span style="color:#81a1c1;">export </span><span>CARGO_HOME</span><span style="color:#81a1c1;">=</span><span style="color:#a3be8c;">"</span><span style="color:#81a1c1;">$</span><span>CARGO_TARGET_DIR</span><span style="color:#a3be8c;">"/cargo-home
</span><span style="color:#81a1c1;">export </span><span>OUTPUT</span><span style="color:#81a1c1;">=</span><span style="color:#a3be8c;">"</span><span style="color:#81a1c1;">$</span><span>3</span><span style="color:#a3be8c;">"
</span><span style="color:#81a1c1;">export </span><span>BUILDTYPE</span><span style="color:#81a1c1;">=</span><span style="color:#a3be8c;">"</span><span style="color:#81a1c1;">$</span><span>4</span><span style="color:#a3be8c;">"
</span><span style="color:#81a1c1;">export </span><span>APP_BIN</span><span style="color:#81a1c1;">=</span><span style="color:#a3be8c;">"</span><span style="color:#81a1c1;">$</span><span>5</span><span style="color:#a3be8c;">"
</span><span>
</span><span>
</span><span style="color:#81a1c1;">if </span><span style="color:#88c0d0;">[[ </span><span style="color:#81a1c1;">$</span><span>BUILDTYPE </span><span style="color:#81a1c1;">= </span><span style="color:#a3be8c;">"release" </span><span style="color:#88c0d0;">]]
</span><span style="color:#81a1c1;">then
</span><span> </span><span style="color:#88c0d0;">echo </span><span style="color:#a3be8c;">"RELEASE MODE"
</span><span> </span><span style="color:#88c0d0;">cargo</span><span> build --manifest-path </span><span style="color:#81a1c1;">\
</span><span> </span><span style="color:#a3be8c;">"</span><span style="color:#81a1c1;">$</span><span>MESON_SOURCE_ROOT</span><span style="color:#a3be8c;">"</span><span>/Cargo.toml --release </span><span style="color:#81a1c1;">&</span><span style="color:#88c0d0;">amp</span><span>;</span><span style="color:#81a1c1;">&</span><span style="color:#88c0d0;">amp</span><span style="color:#81a1c1;">; \
</span><span> </span><span style="color:#88c0d0;">cp </span><span style="color:#a3be8c;">"</span><span style="color:#81a1c1;">$</span><span>CARGO_TARGET_DIR</span><span style="color:#a3be8c;">"</span><span>/release/</span><span style="color:#a3be8c;">"</span><span style="color:#81a1c1;">$</span><span>APP_BIN</span><span style="color:#a3be8c;">" "</span><span style="color:#81a1c1;">$</span><span>OUTPUT</span><span style="color:#a3be8c;">"
</span><span style="color:#81a1c1;">else
</span><span> </span><span style="color:#88c0d0;">echo </span><span style="color:#a3be8c;">"DEBUG MODE"
</span><span> </span><span style="color:#88c0d0;">cargo</span><span> build --manifest-path </span><span style="color:#81a1c1;">\
</span><span> </span><span style="color:#a3be8c;">"</span><span style="color:#81a1c1;">$</span><span>MESON_SOURCE_ROOT</span><span style="color:#a3be8c;">"</span><span>/Cargo.toml --verbose </span><span style="color:#81a1c1;">&</span><span style="color:#88c0d0;">amp</span><span>;</span><span style="color:#81a1c1;">&</span><span style="color:#88c0d0;">amp</span><span style="color:#81a1c1;">; \
</span><span> </span><span style="color:#88c0d0;">cp </span><span style="color:#a3be8c;">"</span><span style="color:#81a1c1;">$</span><span>CARGO_TARGET_DIR</span><span style="color:#a3be8c;">"</span><span>/debug/</span><span style="color:#a3be8c;">"</span><span style="color:#81a1c1;">$</span><span>APP_BIN</span><span style="color:#a3be8c;">" "</span><span style="color:#81a1c1;">$</span><span>OUTPUT</span><span style="color:#a3be8c;">"
</span><span style="color:#81a1c1;">fi
</span></code></pre>
<p>We should have everything by now, and the latest step would be preparing our Flatpak manifest.</p>
<p>We can try that our Meson build is working by typing</p>
<pre data-lang="bash" style="background-color:#2e3440;color:#d8dee9;" class="language-bash "><code class="language-bash" data-lang="bash"><span style="color:#88c0d0;">meson</span><span> _builddir --prefix</span><span style="color:#81a1c1;">=</span><span>/tmp
</span><span style="color:#88c0d0;">ninja</span><span> -C _builddir
</span></code></pre>
<p>Before we start writing our Flatpak manifest, please take a short tour at <a rel="nofollow noreferrer" href="https://docs.flatpak.org/en/latest/">https://docs.flatpak.org/en/latest/</a> to read about the basics of Flatpak.</p>
<p>We will be writing our manifest in JSON, you could use a YAML file too.</p>
<h4 id="flatpak-manifest">Flatpak Manifest</h4>
<pre data-lang="json" style="background-color:#2e3440;color:#d8dee9;" class="language-json "><code class="language-json" data-lang="json"><span>{
</span><span> </span><span style="color:#a3be8c;">"app-id"</span><span style="color:#eceff4;">: </span><span style="color:#a3be8c;">"com.belmoussaoui.RustFlatpak"</span><span style="color:#eceff4;">,
</span><span> </span><span style="color:#a3be8c;">"runtime"</span><span style="color:#eceff4;">: </span><span style="color:#a3be8c;">"org.freedesktop.Platform"</span><span style="color:#eceff4;">,
</span><span> </span><span style="color:#a3be8c;">"runtime-version"</span><span style="color:#eceff4;">: </span><span style="color:#a3be8c;">"20.08"</span><span style="color:#eceff4;">,
</span><span> </span><span style="color:#a3be8c;">"sdk"</span><span style="color:#eceff4;">: </span><span style="color:#a3be8c;">"org.freedesktop.Sdk"</span><span style="color:#eceff4;">,
</span><span> </span><span style="color:#a3be8c;">"sdk-extensions" </span><span style="color:#eceff4;">: </span><span>[
</span><span> </span><span style="color:#a3be8c;">"org.freedesktop.Sdk.Extension.rust-stable"
</span><span> ]</span><span style="color:#eceff4;">,
</span><span> </span><span style="color:#a3be8c;">"command"</span><span style="color:#eceff4;">: </span><span style="color:#a3be8c;">"rust-flatpak"</span><span style="color:#eceff4;">,
</span><span> </span><span style="color:#a3be8c;">"finish-args"</span><span style="color:#eceff4;">: </span><span>[
</span><span> </span><span style="color:#a3be8c;">"--share=ipc"</span><span style="color:#eceff4;">,
</span><span> </span><span style="color:#a3be8c;">"--socket=fallback-x11"</span><span style="color:#eceff4;">,
</span><span> </span><span style="color:#a3be8c;">"--socket=wayland"</span><span style="color:#eceff4;">,
</span><span> </span><span style="color:#a3be8c;">"--device=dri"
</span><span> ]</span><span style="color:#eceff4;">,
</span><span> </span><span style="color:#a3be8c;">"build-options"</span><span style="color:#eceff4;">: </span><span>{
</span><span> </span><span style="color:#a3be8c;">"append-path" </span><span style="color:#eceff4;">: </span><span style="color:#a3be8c;">"/usr/lib/sdk/rust-stable/bin"</span><span style="color:#eceff4;">,
</span><span> </span><span style="color:#a3be8c;">"build-args" </span><span style="color:#eceff4;">: </span><span>[
</span><span> </span><span style="color:#a3be8c;">"--share=network"
</span><span> ]</span><span style="color:#eceff4;">,
</span><span> </span><span style="color:#a3be8c;">"env" </span><span style="color:#eceff4;">: </span><span>{
</span><span> </span><span style="color:#a3be8c;">"CARGO_HOME" </span><span style="color:#eceff4;">: </span><span style="color:#a3be8c;">"/run/build/rust-flatpak/cargo"
</span><span> }
</span><span> }</span><span style="color:#eceff4;">,
</span><span> </span><span style="color:#a3be8c;">"modules"</span><span style="color:#eceff4;">: </span><span>[
</span><span> {
</span><span> </span><span style="color:#a3be8c;">"name"</span><span style="color:#eceff4;">: </span><span style="color:#a3be8c;">"rust-flatpak"</span><span style="color:#eceff4;">,
</span><span> </span><span style="color:#a3be8c;">"buildsystem"</span><span style="color:#eceff4;">: </span><span style="color:#a3be8c;">"meson"</span><span style="color:#eceff4;">,
</span><span> </span><span style="color:#a3be8c;">"sources"</span><span style="color:#eceff4;">: </span><span>[
</span><span> {
</span><span> </span><span style="color:#a3be8c;">"type"</span><span style="color:#eceff4;">: </span><span style="color:#a3be8c;">"dir"</span><span style="color:#eceff4;">,
</span><span> </span><span style="color:#a3be8c;">"path"</span><span style="color:#eceff4;">: </span><span style="color:#a3be8c;">"../"
</span><span> }
</span><span> ]
</span><span> }
</span><span> ]
</span><span>}
</span></code></pre>
<p>Building our application using Flatpak isn’t hard, as the freedesktop runtime gives us already all what we could need to build our app, on top of it we have the Rust SDK extension that adds Rust support.</p>
<p>You can notice that the application is built with network access from the build-args. Flatpak by default, downloads all the sources and don’t give network access to anything during the build process. Except we need Cargo to be able to download our dependencies. Such manifest is the way to go if you’re using it for CI. If your plan is to use it to distribute your application on a Flatpak based store like Flathub, all the dependencies have to be downloaded before starting the build.</p>
<p>Meson allows you to create a tarball of your application using <code>ninja -C _builddir dist</code>. Except, we have to tell Meson that we need to bundle all our Cargo dependencies too, basically calling <code>cargo vendor</code> and moving the tarballs to <code>$DIST</code>.</p>
<pre data-lang="meson" style="background-color:#2e3440;color:#d8dee9;" class="language-meson "><code class="language-meson" data-lang="meson"><span>meson.add_dist_script(
</span><span> 'build-aux/dist-vendor.sh',
</span><span> meson.build_root() / 'meson-dist' / meson.project_name() + '-' + version,
</span><span> meson.source_root()
</span><span>)
</span></code></pre>
<p>And we add our <code>dist-vendor</code> script</p>
<pre data-lang="bash" style="background-color:#2e3440;color:#d8dee9;" class="language-bash "><code class="language-bash" data-lang="bash"><span style="color:#616e88;">#!/bin/sh
</span><span style="color:#81a1c1;">export </span><span>DIST</span><span style="color:#81a1c1;">=</span><span style="color:#a3be8c;">"</span><span style="color:#81a1c1;">$</span><span>1</span><span style="color:#a3be8c;">"
</span><span style="color:#81a1c1;">export </span><span>SOURCE_ROOT</span><span style="color:#81a1c1;">=</span><span style="color:#a3be8c;">"</span><span style="color:#81a1c1;">$</span><span>2</span><span style="color:#a3be8c;">"
</span><span>
</span><span style="color:#88c0d0;">cd </span><span style="color:#a3be8c;">"</span><span style="color:#81a1c1;">$</span><span>SOURCE_ROOT</span><span style="color:#a3be8c;">"
</span><span style="color:#88c0d0;">mkdir </span><span style="color:#a3be8c;">"</span><span style="color:#81a1c1;">$</span><span>DIST</span><span style="color:#a3be8c;">"</span><span>/.cargo
</span><span style="color:#88c0d0;">cargo</span><span> vendor </span><span style="color:#81a1c1;">| </span><span style="color:#88c0d0;">sed </span><span style="color:#a3be8c;">'s/^directory = ".*"/directory = "vendor"/g' </span><span style="color:#81a1c1;">> $</span><span>DIST/.cargo/config
</span><span style="color:#616e88;"># Move vendor into dist tarball directory
</span><span style="color:#88c0d0;">mv</span><span> vendor </span><span style="color:#a3be8c;">"</span><span style="color:#81a1c1;">$</span><span>DIST</span><span style="color:#a3be8c;">"
</span></code></pre>
<p>If we want to create a new release and publish it on Falthub, all we have to do is generate a tarball and upload it to our tagged release on Gitlab/Github/somewhere.</p>
<pre style="background-color:#2e3440;color:#d8dee9;"><code><span>meson -C _builddir
</span><span>ninja -C _builddir/ dist
</span></code></pre>
<p>Which should create a tarball under <code>_builddir/meson-dist/</code>. We can now remove the network access from build-argsand replace our source from a git type to an archive one and we should be ready to publish it on Flathub for a review!</p>
<h3 id="using-flatpak-cargo-generator">Using flatpak-cargo-generator</h3>
<p>Flatpak cargo generator is one of the <a rel="nofollow noreferrer" href="https://github.com/flatpak/flatpak-builder-tools">many</a> community made scripts that allows you to generate a Flatpak manifest of all the dependencies of your application for various programming languages.</p>
<p>The scripts takes your <code>Cargo.lock</code> as an input and generates a manifest that Flatpak can understand from it. Which could work perfectly fine in case you’re using Meson too (you just won’t have to create a dist script for bundling Cargo dependencies).</p>
<p>You can just grab the <code>flatpak-cargo-generator</code> script from <a rel="nofollow noreferrer" href="https://github.com/flatpak/flatpak-builder-tools/blob/master/cargo/flatpak-cargo-generator.py">https://github.com/flatpak/flatpak-builder-tools/blob/master/cargo/flatpak-cargo-generator.py</a> and run it with</p>
<pre data-lang="bash" style="background-color:#2e3440;color:#d8dee9;" class="language-bash "><code class="language-bash" data-lang="bash"><span style="color:#88c0d0;">python3</span><span> ./flatpak-cargo-generator.py ./Cargo.lock -o cargo-sources.json
</span></code></pre>
<p>The script depends on the python module <code>siphash</code></p>
<pre data-lang="json" style="background-color:#2e3440;color:#d8dee9;" class="language-json "><code class="language-json" data-lang="json"><span>{
</span><span> </span><span style="color:#a3be8c;">"app-id"</span><span style="color:#eceff4;">: </span><span style="color:#a3be8c;">"com.belmoussaoui.RustFlatpak"</span><span style="color:#eceff4;">,
</span><span> </span><span style="color:#a3be8c;">"runtime"</span><span style="color:#eceff4;">: </span><span style="color:#a3be8c;">"org.freedesktop.Platform"</span><span style="color:#eceff4;">,
</span><span> </span><span style="color:#a3be8c;">"runtime-version"</span><span style="color:#eceff4;">: </span><span style="color:#a3be8c;">"20.08"</span><span style="color:#eceff4;">,
</span><span> </span><span style="color:#a3be8c;">"sdk"</span><span style="color:#eceff4;">: </span><span style="color:#a3be8c;">"org.freedesktop.Sdk"</span><span style="color:#eceff4;">,
</span><span> </span><span style="color:#a3be8c;">"sdk-extensions" </span><span style="color:#eceff4;">: </span><span>[
</span><span> </span><span style="color:#a3be8c;">"org.freedesktop.Sdk.Extension.rust-stable"
</span><span> ]</span><span style="color:#eceff4;">,
</span><span> </span><span style="color:#a3be8c;">"command"</span><span style="color:#eceff4;">: </span><span style="color:#a3be8c;">"rust-flatpak"</span><span style="color:#eceff4;">,
</span><span> </span><span style="color:#a3be8c;">"finish-args"</span><span style="color:#eceff4;">: </span><span>[
</span><span> </span><span style="color:#a3be8c;">"--share=ipc"</span><span style="color:#eceff4;">,
</span><span> </span><span style="color:#a3be8c;">"--socket=fallback-x11"</span><span style="color:#eceff4;">,
</span><span> </span><span style="color:#a3be8c;">"--socket=wayland"</span><span style="color:#eceff4;">,
</span><span> </span><span style="color:#a3be8c;">"--device=dri"
</span><span> ]</span><span style="color:#eceff4;">,
</span><span> </span><span style="color:#a3be8c;">"build-options"</span><span style="color:#eceff4;">: </span><span>{
</span><span> </span><span style="color:#a3be8c;">"append-path" </span><span style="color:#eceff4;">: </span><span style="color:#a3be8c;">"/usr/lib/sdk/rust-stable/bin"</span><span style="color:#eceff4;">,
</span><span> </span><span style="color:#a3be8c;">"env" </span><span style="color:#eceff4;">: </span><span>{
</span><span> </span><span style="color:#a3be8c;">"CARGO_HOME" </span><span style="color:#eceff4;">: </span><span style="color:#a3be8c;">"/run/build/rust-flatpak/cargo"
</span><span> }
</span><span> }</span><span style="color:#eceff4;">,
</span><span> </span><span style="color:#a3be8c;">"modules"</span><span style="color:#eceff4;">: </span><span>[
</span><span> {
</span><span> </span><span style="color:#a3be8c;">"name"</span><span style="color:#eceff4;">: </span><span style="color:#a3be8c;">"rust-flatpak"</span><span style="color:#eceff4;">,
</span><span> </span><span style="color:#a3be8c;">"buildsystem"</span><span style="color:#eceff4;">: </span><span style="color:#a3be8c;">"simple"</span><span style="color:#eceff4;">,
</span><span> </span><span style="color:#a3be8c;">"build-commands"</span><span style="color:#eceff4;">: </span><span>[
</span><span> </span><span style="color:#a3be8c;">"cargo --offline fetch --manifest-path Cargo.toml --verbose"</span><span style="color:#eceff4;">,
</span><span> </span><span style="color:#a3be8c;">"cargo --offline build --release --verbose"</span><span style="color:#eceff4;">,
</span><span> </span><span style="color:#a3be8c;">"install -Dm755 ./target/debug/rust-flatpak -t /app/bin/"</span><span style="color:#eceff4;">,
</span><span> </span><span style="color:#a3be8c;">"install -Dm644 ./data/${FLATPAK_ID}.metainfo.xml -t /app/share/metainfo/"</span><span style="color:#eceff4;">,
</span><span> </span><span style="color:#a3be8c;">"install -Dm644 ./data/${FLATPAK_ID}.desktop -t /app/share/applications/"</span><span style="color:#eceff4;">,
</span><span> </span><span style="color:#a3be8c;">"install -Dm644 ./data/${FLATPAK_ID}.svg -t /app/share/icons/hicolor/scalable/apps/"
</span><span> ]</span><span style="color:#eceff4;">,
</span><span> </span><span style="color:#a3be8c;">"sources"</span><span style="color:#eceff4;">: </span><span>[
</span><span> {
</span><span> </span><span style="color:#a3be8c;">"type"</span><span style="color:#eceff4;">: </span><span style="color:#a3be8c;">"dir"</span><span style="color:#eceff4;">,
</span><span> </span><span style="color:#a3be8c;">"path"</span><span style="color:#eceff4;">: </span><span style="color:#a3be8c;">"../"
</span><span> }</span><span style="color:#eceff4;">,
</span><span> </span><span style="color:#a3be8c;">"generated-sources.json"
</span><span> ]
</span><span> }
</span><span> ]
</span><span>}
</span></code></pre>
<p>Where the <code>generated-sources.json</code> is a the generated file using <code>flatpak-cargo-generator</code></p>
<h3 id="trying-your-flatpak-manifest">Trying your Flatpak manifest</h3>
<p>You can build and test the Flatpak package locally using flatpak-builder</p>
<pre data-lang="bash" style="background-color:#2e3440;color:#d8dee9;" class="language-bash "><code class="language-bash" data-lang="bash"><span style="color:#88c0d0;">flatpak-builder</span><span> --install repo build-aux/com.belmoussaoui.RustFlatpak.json --force-clean --user -y
</span></code></pre>
<p>You can run the application later using</p>
<pre data-lang="bash" style="background-color:#2e3440;color:#d8dee9;" class="language-bash "><code class="language-bash" data-lang="bash"><span style="color:#88c0d0;">flatpak</span><span> run com.belmoussaoui.RustFlatpak
</span></code></pre>
<p>Publishing the application later on Flathub should be straight forward. You can read more about that from here</p>
<h2 id="bonus-gitlab-ci-github-actions-bundles">Bonus: Gitlab CI/Github Actions bundles</h2>
<p>At GNOME we have a CI template that makes integrating a Flatpak pipeline pretty easy. It requires using Meson as a build system, but you can easily tweak that to your build preferences. Sadly, we can’t achieve similar results easily with Github Actions for now.</p>
<pre data-lang="yaml" style="background-color:#2e3440;color:#d8dee9;" class="language-yaml "><code class="language-yaml" data-lang="yaml"><span style="color:#8fbcbb;">include</span><span style="color:#eceff4;">:
</span><span> - </span><span style="color:#8fbcbb;">project</span><span style="color:#eceff4;">: </span><span style="color:#a3be8c;">'gnome/citemplates'
</span><span> </span><span style="color:#8fbcbb;">file</span><span style="color:#eceff4;">: </span><span style="color:#a3be8c;">'flatpak/flatpak-ci-initiative-sdk-extensions.yml'
</span><span>
</span><span style="color:#8fbcbb;">flatpak</span><span style="color:#eceff4;">:
</span><span> </span><span style="color:#8fbcbb;">image</span><span style="color:#eceff4;">: </span><span style="color:#a3be8c;">'registry.gitlab.gnome.org/gnome/gnome-runtime-images/rust_bundle:3.38'
</span><span> </span><span style="color:#8fbcbb;">variables</span><span style="color:#eceff4;">:
</span><span> </span><span style="color:#8fbcbb;">BUNDLE</span><span style="color:#eceff4;">: </span><span style="color:#a3be8c;">"rust-flatpak-nightly.flatpak"
</span><span> </span><span style="color:#8fbcbb;">MANIFEST_PATH</span><span style="color:#eceff4;">: </span><span style="color:#a3be8c;">"build-aux/com.belmoussaoui.RustFlatpak.json"
</span><span> </span><span style="color:#8fbcbb;">FLATPAK_MODULE</span><span style="color:#eceff4;">: </span><span style="color:#a3be8c;">"rust-flatpak"
</span><span> </span><span style="color:#8fbcbb;">APP_ID</span><span style="color:#eceff4;">: </span><span style="color:#a3be8c;">"com.belmoussaoui.RustFlatpak"
</span><span> </span><span style="color:#8fbcbb;">RUNTIME_REPO</span><span style="color:#eceff4;">: </span><span style="color:#a3be8c;">"https://flathub.org/repo/flathub.flatpakrepo"
</span><span> </span><span style="color:#8fbcbb;">extends</span><span style="color:#eceff4;">: </span><span style="color:#a3be8c;">'.flatpak'
</span></code></pre>
<p>If you're using Github , you can use the <a rel="nofollow noreferrer" href="https://github.com/marketplace/actions/flatpak-builder">flatpak-github-actions</a>. See below for an example</p>
<pre data-lang="yaml" style="background-color:#2e3440;color:#d8dee9;" class="language-yaml "><code class="language-yaml" data-lang="yaml"><span style="color:#81a1c1;">on</span><span style="color:#eceff4;">:
</span><span> </span><span style="color:#8fbcbb;">push</span><span style="color:#eceff4;">:
</span><span> </span><span style="color:#8fbcbb;">branches</span><span style="color:#eceff4;">: </span><span>[</span><span style="color:#a3be8c;">master</span><span>]
</span><span> </span><span style="color:#8fbcbb;">pull_request</span><span style="color:#eceff4;">:
</span><span style="color:#8fbcbb;">name</span><span style="color:#eceff4;">: </span><span style="color:#a3be8c;">CI
</span><span style="color:#8fbcbb;">jobs</span><span style="color:#eceff4;">:
</span><span> </span><span style="color:#8fbcbb;">flatpak-builder</span><span style="color:#eceff4;">:
</span><span> </span><span style="color:#8fbcbb;">name</span><span style="color:#eceff4;">: </span><span style="color:#a3be8c;">"Flatpak Builder"
</span><span> </span><span style="color:#8fbcbb;">runs-on</span><span style="color:#eceff4;">: </span><span style="color:#a3be8c;">ubuntu-latest
</span><span> </span><span style="color:#8fbcbb;">container</span><span style="color:#eceff4;">:
</span><span> </span><span style="color:#8fbcbb;">image</span><span style="color:#eceff4;">: </span><span style="color:#a3be8c;">bilelmoussaoui/flatpak-github-actions:gnome-3.38
</span><span> </span><span style="color:#8fbcbb;">options</span><span style="color:#eceff4;">: </span><span style="color:#a3be8c;">--privileged
</span><span> </span><span style="color:#8fbcbb;">steps</span><span style="color:#eceff4;">:
</span><span> - </span><span style="color:#8fbcbb;">uses</span><span style="color:#eceff4;">: </span><span style="color:#a3be8c;">actions/checkout@v2
</span><span> - </span><span style="color:#8fbcbb;">uses</span><span style="color:#eceff4;">: </span><span style="color:#a3be8c;">bilelmoussaoui/flatpak-github-actions@v1
</span><span> </span><span style="color:#8fbcbb;">with</span><span style="color:#eceff4;">:
</span><span> </span><span style="color:#8fbcbb;">bundle</span><span style="color:#eceff4;">: </span><span style="color:#a3be8c;">"rust-flatpak-nightly.flatpak"
</span><span> </span><span style="color:#8fbcbb;">manifest-path</span><span style="color:#eceff4;">: </span><span style="color:#a3be8c;">"build-aux/com.belmoussaoui.RustFlatpak.json"
</span></code></pre>
<p>That should be it 🙂 You should have the basics to publish a simple Rust application as a Flatpak. Useful links:</p>
<ul>
<li>
<p>Flatpak Docs: <a rel="nofollow noreferrer" href="https://docs.flatpak.org/en/latest/">https://docs.flatpak.org/en/latest/</a></p>
</li>
<li>
<p>Flatpak Builder Tools: <a rel="nofollow noreferrer" href="https://github.com/flatpak/flatpak-builder-tools">https://github.com/flatpak/flatpak-builder-tools</a></p>
</li>
<li>
<p>Meson: <a rel="nofollow noreferrer" href="https://mesonbuild.com/">https://mesonbuild.com/</a></p>
</li>
<li>
<p>Flathub publishing guides: <a rel="nofollow noreferrer" href="https://github.com/flathub/flathub/wiki/App-Submission">https://github.com/flathub/flathub/wiki/App-Submission</a></p>
</li>
<li>
<p>GTK Rust bindings: <a rel="nofollow noreferrer" href="https://gtk-rs.org/">https://gtk-rs.org/</a></p>
</li>
<li>
<p>GTK + Rust + Meson + Flatpak template: <a rel="nofollow noreferrer" href="https://gitlab.gnome.org/bilelmoussaoui/gtk-rust-template">https://gitlab.gnome.org/bilelmoussaoui/gtk-rust-template</a></p>
</li>
</ul>
Read It Later, yet an other GTK & Rust application2020-02-04T00:00:00+00:002020-02-04T00:00:00+00:00
Unknown
https://belmoussaoui.com/blog/7-first-release-read-it-later/<p>Late last year, I wanted to find an application that I can try the Rust async features with it. I started looking for a service that had a nice Rust API wrapper already and I started prototyping Read It Later on top of <a rel="nofollow noreferrer" href="https://sr.ht/~swalladge/wallabag-api/">wallabag-rs</a>.</p>
<p>Read It Later, is a well designed <a rel="nofollow noreferrer" href="https://www.wallabag.it/en">Wallabag</a> client. It’s built with Rust, GTK & libhandy on the UI side. It’s fully adaptive which makes it Linux on pocket ready and also comes with a beautiful icon designed by <a rel="nofollow noreferrer" href="https://tobiasbernard.com/">Tobias Bernard</a>.</p>
<p><img src="/posts/7-first-release-read-it-later/image.png" alt="App Icon Preview" /></p>
<p>Screenshot taken using App Icon Preview</p>
<p>It allows you to do the basic things:</p>
<ul>
<li>Login/Logout/Sync</li>
<li>Add/Favorite/Archive/Delete an article</li>
<li>Dark Mode Reader</li>
<li>Adaptive UI</li>
</ul>
<p><img src="/posts/7-first-release-read-it-later/Screenshot-from-2020-02-05-20-15-48.png" alt="Login View" /></p>
<p><img src="/posts/7-first-release-read-it-later/screenshot1.png" alt="Articles View" /></p>
<p><img src="/posts/7-first-release-read-it-later/screenshot2.png" alt="Article View" /></p>
<p><img src="/posts/7-first-release-read-it-later/screenshot3.png" alt="Adaptive View" /></p>
<p>You can grab it already from Flathub at <a rel="nofollow noreferrer" href="https://flathub.org/apps/details/com.belmoussaoui.ReadItLater">https://flathub.org/apps/details/com.belmoussaoui.ReadItLater</a>. If you would like to contribute, the source code is available at <a rel="nofollow noreferrer" href="https://gitlab.gnome.org/bilelmoussaoui/read-it-later">https://gitlab.gnome.org/bilelmoussaoui/read-it-later</a></p>
<p>So far, writing apps with GTK & Rust have been a fun experience. GNOME Builder & Flatpak made the developer experience smooth 🙂</p>
Build win32/win64 nightlies using Gitlab CI2019-09-30T00:00:00+00:002019-09-30T00:00:00+00:00
Unknown
https://belmoussaoui.com/blog/6-build-win32-win64-nightlies-using-gitlab-ci/<p>A week ago after getting Dia nightlies published on GNOME's new Flatpak nightlies infrastructure I was discussing with Zander Brown, the new maintainer of Dia, of the possibility to publish Windows nightlies through Gitlab the same way we do with Flatpak bundles. A few minutes later I was already trying to cargo-cult what Gedit had to build Windows bundles.</p>
<p>It took me a bit of time to figure out how things work, especially that I wanted to make it easier for you to set up a win32/win64 build for your project without much work and as we already use a CI template for Flatpak builds, I ended up doing something pretty similar, but a bit more complex under the hood.</p>
<figure>
<img src="/posts/6-build-win32-win64-nightlies-using-gitlab-ci/Screenshot-from-2019-09-30-05-22-58.png" >
<figcaption>Gitlab CI buildings Win32/Win64 bundles</figcaption>
</figure>
<p><img src="/posts/6-build-win32-win64-nightlies-using-gitlab-ci/Screenshot-from-2019-09-30-05-41-25.png" alt="CI Artifacts" /></p>
<h2 id="how-it-works">How it works?</h2>
<p>We are using <a rel="nofollow noreferrer" href="http://www.mingw.org/">MinGW</a> (Minimalist GNU Windows) under the hood, we build a "chroot" and install all the basic dependencies. Once that's done, we need to build the application and for that we use a PKGBUILD file which we configure and build to create a package that we installer later on the chroot directory. After that, we remove few unneeded stuff like docs, metainfo, desktop files, unneeded executable files (we still need to improve that) and we have our package ready to pass it to <a rel="nofollow noreferrer" href="https://wixtoolset.org/">Wix Toolset</a> (Windows Installer XML Toolset). Wix Toolset is a pretty complicated thing and I have just put things together to make it work :P It what creates the installer for us, the entry on the start menu and so on. As it's still a WIP project, things might change a bit in the future.</p>
<figure>
<img src="/posts/6-build-win32-win64-nightlies-using-gitlab-ci/Screenshot-from-2019-09-30-05-58-35.png" >
<figcaption>Palette running from a Windows 10 VM on GNOME Boxes</figcaption>
</figure><h2 id="how-to-use-it-on-your-project">How to use it on your project?</h2>
<p>The current state of the project isn't ready to be used yet, the produced bundles are a bit large and I need to handle the dependencies a bit better. But it should be something like this once released for the public usage:</p>
<p>In order to setup a Gitlab CI that creates a nightly windows bundles for you all you have to do is create a <code>.gitlab-ci.yml</code> in the source tree of your project with something equivalent to this</p>
<pre data-lang="yaml" style="background-color:#2e3440;color:#d8dee9;" class="language-yaml "><code class="language-yaml" data-lang="yaml"><span style="color:#8fbcbb;">include</span><span style="color:#eceff4;">: </span><span style="color:#a3be8c;">'https://gitlab.gnome.org/bilelmoussaoui/win32-ci-template/raw/master/ci-template.yml'
</span><span>
</span><span style="color:#8fbcbb;">variables</span><span style="color:#eceff4;">:
</span><span> </span><span style="color:#8fbcbb;">GIT_SUBMODULE_STRATEGY</span><span style="color:#eceff4;">: </span><span style="color:#a3be8c;">recursive
</span><span> </span><span style="color:#8fbcbb;">APP_NAME</span><span style="color:#eceff4;">: </span><span style="color:#a3be8c;">"Palette"
</span><span> </span><span style="color:#8fbcbb;">BIN_NAME</span><span style="color:#eceff4;">: </span><span style="color:#a3be8c;">"org.gnome.zbrown.Palette"
</span><span> </span><span style="color:#8fbcbb;">MSI_BUNDLE</span><span style="color:#eceff4;">: </span><span style="color:#a3be8c;">"palette.msi"
</span><span> </span><span style="color:#8fbcbb;">MANUFACTURER</span><span style="color:#eceff4;">: </span><span style="color:#a3be8c;">"GNOME"
</span><span> </span><span style="color:#8fbcbb;">MESON_ARGS</span><span style="color:#eceff4;">: </span><span style="color:#a3be8c;">""
</span><span> </span><span style="color:#8fbcbb;">VERSION</span><span style="color:#eceff4;">: </span><span style="color:#a3be8c;">"0.1"
</span><span> </span><span style="color:#8fbcbb;">DEPENDENCIES</span><span style="color:#eceff4;">: </span><span style="color:#a3be8c;">"vala gtk3 gobject-introspection gettext appstream-glib meson ninja"
</span><span> </span><span style="color:#8fbcbb;">ICON_PATH</span><span style="color:#eceff4;">: </span><span style="color:#a3be8c;">"data/org.gnome.zbrown.Palette.svg"
</span><span>
</span><span style="color:#8fbcbb;">win32</span><span style="color:#eceff4;">:
</span><span> </span><span style="color:#8fbcbb;">extends</span><span style="color:#eceff4;">: </span><span style="color:#a3be8c;">.win32
</span><span>
</span><span style="color:#8fbcbb;">win64</span><span style="color:#eceff4;">:
</span><span> </span><span style="color:#8fbcbb;">extends</span><span style="color:#eceff4;">: </span><span style="color:#a3be8c;">.win64
</span></code></pre>
<h2 id="the-plans">The plans</h2>
<p>I have a lot of plans to improve this and allow the applications that used/want to ship a windows bundle do that with the less work possible. Here's my roadmap for the first "public" release</p>
<ul>
<li>
<p>Reduce bundle size</p>
</li>
<li>
<p>Correctly handle dependencies</p>
</li>
<li>
<p>Use a container to reduce the downloaded packages for each build</p>
</li>
</ul>
<p>Any of this wouldn't be possible without the work from MinGW, Gedit developers/contributors who made the installer in the past and StackOverflow.</p>
<p>Source code: <a rel="nofollow noreferrer" href="https://gitlab.gnome.org/bilelmoussaoui/win32-ci-template/">https://gitlab.gnome.org/bilelmoussaoui/win32-ci-template/</a></p>
<p>If you want to help me improve this "faster" you can either buy me a coffee to keep me awake like tonight (it's 6am and still awake) you can do that from here <a rel="nofollow noreferrer" href="https://paypal.me/BilalELMoussaoui">https://paypal.me/BilalELMoussaoui</a></p>
<p>See you soon with more awesome stuff!</p>
<p><img src="/posts/6-build-win32-win64-nightlies-using-gitlab-ci/Screenshot-from-2019-09-30-02-46-48.png" alt="Palette Windows Installer" /></p>
Store and display GeoJSON data using Flask, MongoDB & Leaflet2019-06-28T00:00:00+00:002019-06-28T00:00:00+00:00
Unknown
https://belmoussaoui.com/blog/5-display-geojson-data-using-flask-and-mongodb/<p><a rel="nofollow noreferrer" href="http://flask.pocoo.org/">Flask</a> is a lightweight <strong>Python</strong> framework (that can somehow be compared to <strong>Slim framework</strong> for <strong>PHP</strong>). Additional functionalities can be added by installing Flask extensions. <strong>GeoJSON</strong> is a format that allows us to encode geolocation data as <strong>JSON</strong>. You can read more about the specifications <a rel="nofollow noreferrer" href="https://geojson.org/">here</a> and play with it a bit on this very well made website.</p>
<p>The usage of <strong>MongoDB</strong> is not arbitrary here, as it allows us to store geolocation data easily. You might need to install it on your system before following this article.</p>
<p>Let's set up a new virtual environment using <strong>virtualenv</strong></p>
<pre data-lang="bash" style="background-color:#2e3440;color:#d8dee9;" class="language-bash "><code class="language-bash" data-lang="bash"><span style="color:#88c0d0;">virtualenv</span><span> flask-mongodb
</span><span style="color:#88c0d0;">source</span><span> ./flask-mongodb/bin/activate
</span></code></pre>
<p>We can now install the required packages</p>
<pre data-lang="bash" style="background-color:#2e3440;color:#d8dee9;" class="language-bash "><code class="language-bash" data-lang="bash"><span style="color:#88c0d0;">pip3</span><span> install flask
</span><span style="color:#88c0d0;">pip3</span><span> install pymongo
</span></code></pre>
<h2 id="our-first-website-using-flask">Our first website using Flask</h2>
<p>Building a plain simple website using Flask is pretty straight forward as we don't have to spend hours configuring stuff that we might never use.</p>
<pre data-lang="python" style="background-color:#2e3440;color:#d8dee9;" class="language-python "><code class="language-python" data-lang="python"><span style="color:#81a1c1;">from </span><span>flask </span><span style="color:#81a1c1;">import </span><span>Flask
</span><span>app </span><span style="color:#81a1c1;">= </span><span style="color:#88c0d0;">Flask</span><span>(__name__)
</span><span>
</span><span style="color:#d08770;">@</span><span>app</span><span style="color:#81a1c1;">.</span><span>route(</span><span style="color:#a3be8c;">'/'</span><span>)
</span><span style="color:#81a1c1;">def </span><span style="color:#88c0d0;">main</span><span>():
</span><span> </span><span style="color:#81a1c1;">return </span><span style="color:#a3be8c;">'Hello world!'
</span><span>
</span><span>app</span><span style="color:#81a1c1;">.</span><span style="color:#88c0d0;">run</span><span>()
</span></code></pre>
<p>Let's run our <strong>WSGI</strong> application using python3 main.py and head to https://127.0.0.1:5000/ to see our beautiful website.</p>
<p>Flask uses <strong>Jinja2</strong> as a templates engine, it allow us to render HTML templates. It uses the templates directory by default, so let's create that and add a simple HTML file.</p>
<pre data-lang="html" style="background-color:#2e3440;color:#d8dee9;" class="language-html "><code class="language-html" data-lang="html"><span style="color:#81a1c1;"><!</span><span style="color:#eceff4;">DOCTYPE </span><span style="color:#81a1c1;">html>
</span><span style="color:#81a1c1;"><head>
</span><span style="color:#eceff4;"> </span><span style="color:#81a1c1;"><title></span><span style="color:#eceff4;">GeoJSON + Flask + MongoDB</span><span style="color:#81a1c1;"></title>
</span><span style="color:#eceff4;"> </span><span style="color:#81a1c1;"><meta </span><span style="color:#8fbcbb;">charset</span><span style="color:#eceff4;">=</span><span style="color:#a3be8c;">"utf-8"</span><span style="color:#81a1c1;">>
</span><span style="color:#81a1c1;"></head>
</span><span style="color:#81a1c1;"><body>
</span><span style="color:#eceff4;"> </span><span style="color:#81a1c1;"><h1>
</span><span style="color:#eceff4;"> Let's display some nice maps here!
</span><span style="color:#eceff4;"> </span><span style="color:#81a1c1;"></h1>
</span><span style="color:#81a1c1;"></body>
</span><span style="color:#81a1c1;"></html>
</span></code></pre>
<p>We can use the render_template function to render an HTML file which will render (pretty obvious from the function name) and create a response from that HTML content.</p>
<pre data-lang="python" style="background-color:#2e3440;color:#d8dee9;" class="language-python "><code class="language-python" data-lang="python"><span style="color:#81a1c1;">from </span><span>flask </span><span style="color:#81a1c1;">import </span><span>Flask</span><span style="color:#eceff4;">, </span><span>render_template
</span><span>app </span><span style="color:#81a1c1;">= </span><span style="color:#88c0d0;">Flask</span><span>(__name__)
</span><span>
</span><span style="color:#d08770;">@</span><span>app</span><span style="color:#81a1c1;">.</span><span>route(</span><span style="color:#a3be8c;">'/'</span><span>)
</span><span style="color:#81a1c1;">def </span><span style="color:#88c0d0;">main</span><span>():
</span><span> </span><span style="color:#81a1c1;">return </span><span style="color:#88c0d0;">render_template</span><span>(</span><span style="color:#a3be8c;">'main.html'</span><span>)
</span><span>
</span><span>app</span><span style="color:#81a1c1;">.</span><span style="color:#88c0d0;">run</span><span>()
</span></code></pre>
<h2 id="pymongo-the-mongodb-python-api">Pymongo, the MongoDB Python API</h2>
<p><strong>Pymongo</strong> is the Python implementation of the MongoDB API, that can allow us to communicate with mongodb using Python. The mongdb daemon must be running during that process. If it's your first time with your pymongo, have a look at the <a rel="nofollow noreferrer" href="https://pymongo.readthedocs.io/en/stable/tutorial.html">official documentation</a>.</p>
<p>Connecting to a MongoDB database is done by accessing the database name attribute from the client object.</p>
<pre data-lang="python" style="background-color:#2e3440;color:#d8dee9;" class="language-python "><code class="language-python" data-lang="python"><span style="color:#81a1c1;">from </span><span>pymongo </span><span style="color:#81a1c1;">import </span><span>MongoClient
</span><span>
</span><span>client </span><span style="color:#81a1c1;">= </span><span style="color:#88c0d0;">MongoClient</span><span>()
</span><span>db </span><span style="color:#81a1c1;">= </span><span>client</span><span style="color:#81a1c1;">.</span><span>geojson_flask
</span></code></pre>
<p>Let's add some geospatial data that we can display later.</p>
<pre data-lang="python" style="background-color:#2e3440;color:#d8dee9;" class="language-python "><code class="language-python" data-lang="python"><span>addresses </span><span style="color:#81a1c1;">= </span><span>db</span><span style="color:#81a1c1;">.</span><span>addresses_collection
</span><span>addresses</span><span style="color:#81a1c1;">.</span><span style="color:#88c0d0;">insert_one</span><span>({
</span><span> </span><span style="color:#a3be8c;">'type'</span><span style="color:#eceff4;">: </span><span style="color:#a3be8c;">'Point'</span><span style="color:#eceff4;">,
</span><span> </span><span style="color:#a3be8c;">'coordinates'</span><span style="color:#eceff4;">: </span><span>[</span><span style="color:#b48ead;">41</span><span style="color:#eceff4;">.</span><span style="color:#b48ead;">39826</span><span style="color:#eceff4;">, </span><span style="color:#b48ead;">17</span><span style="color:#eceff4;">.</span><span style="color:#b48ead;">13559</span><span>]
</span><span>})
</span></code></pre>
<p>GeoJSON supports multiple objects types like Point, LineString, Polygon... You can find the list of those and how to store them on a MongoDB collection <a rel="nofollow noreferrer" href="https://docs.mongodb.com/manual/reference/geojson/">here</a> or by reading the GeoJSON <a rel="nofollow noreferrer" href="https://tools.ietf.org/html/rfc7946">specs</a>.</p>
<p>Now that we have some data stored on our collection, we need to provide a simple API to fetch that data so we can display it later on our HTML page, we will see the reason later. Our API endpoint should return a JSON object with all the points coordinates we have stored in our collection.</p>
<pre data-lang="python" style="background-color:#2e3440;color:#d8dee9;" class="language-python "><code class="language-python" data-lang="python"><span style="color:#81a1c1;">from </span><span>flask </span><span style="color:#81a1c1;">import </span><span>jsonify
</span><span style="color:#d08770;">@</span><span>app</span><span style="color:#81a1c1;">.</span><span>route(</span><span style="color:#a3be8c;">'/points'</span><span style="color:#eceff4;">, </span><span>methods</span><span style="color:#81a1c1;">=</span><span>[</span><span style="color:#a3be8c;">'GET'</span><span>])
</span><span style="color:#81a1c1;">def </span><span style="color:#88c0d0;">get_all_points</span><span>():
</span><span> points </span><span style="color:#81a1c1;">= </span><span>[]
</span><span> </span><span style="color:#81a1c1;">for </span><span>address </span><span style="color:#81a1c1;">in </span><span>addresses</span><span style="color:#81a1c1;">.</span><span style="color:#88c0d0;">find</span><span>({</span><span style="color:#a3be8c;">'type'</span><span style="color:#eceff4;">: </span><span style="color:#a3be8c;">'Point'</span><span>}):
</span><span> points</span><span style="color:#81a1c1;">.</span><span style="color:#88c0d0;">append</span><span>({
</span><span> </span><span style="color:#a3be8c;">"type"</span><span style="color:#eceff4;">: </span><span style="color:#a3be8c;">"Feature"</span><span style="color:#eceff4;">,
</span><span> </span><span style="color:#a3be8c;">"geometry"</span><span style="color:#eceff4;">: </span><span>{
</span><span> </span><span style="color:#a3be8c;">"type"</span><span style="color:#eceff4;">: </span><span style="color:#a3be8c;">"Point"</span><span style="color:#eceff4;">,
</span><span> </span><span style="color:#a3be8c;">"coordinates"</span><span style="color:#eceff4;">: </span><span>address[</span><span style="color:#a3be8c;">'coordinates'</span><span>]
</span><span> }
</span><span> })
</span><span> </span><span style="color:#81a1c1;">return </span><span style="color:#88c0d0;">jsonify</span><span>(points)
</span></code></pre>
<p>Flask provides a jsonify method which converts our dict object to a JSON object and adds the correct content-type header.</p>
<h2 id="displaying-the-data-with-openstreetmap-leaflet">Displaying the data with OpenStreetMap & Leaflet</h2>
<p>In order to display our map, we will be using <a rel="nofollow noreferrer" href="https://leafletjs.com/">Leaflet</a>, which is a JS library that allows us to interact with maps easily. We won't be using any JS build system here, if you are using VueJS in your project, you can use Vue2Leaflet.</p>
<p>Let's add the JS & CSS files to our HTML file</p>
<pre data-lang="html" style="background-color:#2e3440;color:#d8dee9;" class="language-html "><code class="language-html" data-lang="html"><span style="color:#81a1c1;"><link </span><span style="color:#8fbcbb;">rel</span><span style="color:#eceff4;">=</span><span style="color:#a3be8c;">"stylesheet" </span><span style="color:#8fbcbb;">href</span><span style="color:#eceff4;">=</span><span style="color:#a3be8c;">"https://unpkg.com/leaflet@1.5.1/dist/leaflet.css" </span><span style="color:#81a1c1;">/>
</span><span style="color:#81a1c1;"><script </span><span style="color:#8fbcbb;">src</span><span style="color:#eceff4;">=</span><span style="color:#a3be8c;">"https://unpkg.com/leaflet@1.5.1/dist/leaflet.js"</span><span style="color:#81a1c1;">></script>
</span></code></pre>
<p>We can now create a new map using Leaflet and pass the center position and the zoom level.</p>
<pre data-lang="html" style="background-color:#2e3440;color:#d8dee9;" class="language-html "><code class="language-html" data-lang="html"><span style="color:#81a1c1;"><div </span><span style="color:#8fbcbb;">id</span><span style="color:#eceff4;">=</span><span style="color:#a3be8c;">"map" </span><span style="color:#8fbcbb;">style</span><span style="color:#eceff4;">=</span><span style="color:#a3be8c;">"</span><span>height</span><span style="color:#eceff4;">: </span><span style="color:#b48ead;">80</span><span style="color:#81a1c1;">vh</span><span style="color:#eceff4;">;</span><span style="color:#a3be8c;">"</span><span style="color:#81a1c1;">></div>
</span><span style="color:#eceff4;">
</span><span style="color:#81a1c1;"><script>
</span><span style="color:#81a1c1;">let </span><span>map </span><span style="color:#81a1c1;">= </span><span style="color:#8fbcbb;">L</span><span style="color:#81a1c1;">.</span><span style="color:#88c0d0;">map</span><span style="color:#eceff4;">(</span><span style="color:#a3be8c;">'map'</span><span style="color:#eceff4;">)</span><span style="color:#81a1c1;">.</span><span style="color:#88c0d0;">setView</span><span style="color:#eceff4;">([</span><span style="color:#b48ead;">51</span><span style="color:#eceff4;">.</span><span style="color:#b48ead;">505</span><span style="color:#eceff4;">, </span><span style="color:#81a1c1;">-</span><span style="color:#b48ead;">0</span><span style="color:#eceff4;">.</span><span style="color:#b48ead;">09</span><span style="color:#eceff4;">], </span><span style="color:#b48ead;">13</span><span style="color:#eceff4;">)
</span><span style="color:#81a1c1;"></script>
</span></code></pre>
<p>If you want to center the map on the user's current location, you can use this, which will ask the user to allow your website to access their current geolocation and update the center to that position.</p>
<pre data-lang="javascript" style="background-color:#2e3440;color:#d8dee9;" class="language-javascript "><code class="language-javascript" data-lang="javascript"><span style="color:#81a1c1;">var </span><span>map </span><span style="color:#81a1c1;">= L.</span><span style="color:#88c0d0;">map</span><span>(</span><span style="color:#a3be8c;">'map'</span><span>)</span><span style="color:#81a1c1;">.</span><span style="color:#88c0d0;">setView</span><span>([</span><span style="color:#b48ead;">51.505</span><span style="color:#eceff4;">, </span><span style="color:#81a1c1;">-</span><span style="color:#b48ead;">0.09</span><span>]</span><span style="color:#eceff4;">, </span><span style="color:#b48ead;">13</span><span>)
</span><span style="color:#81a1c1;">if </span><span>(navigator</span><span style="color:#81a1c1;">.</span><span>geolocation) {
</span><span> navigator</span><span style="color:#81a1c1;">.</span><span>geolocation</span><span style="color:#81a1c1;">.</span><span style="color:#88c0d0;">getCurrentPosition</span><span>((position) </span><span style="color:#81a1c1;">=> </span><span>{
</span><span> map</span><span style="color:#81a1c1;">.</span><span style="color:#88c0d0;">panTo</span><span>(</span><span style="color:#81a1c1;">L.</span><span style="color:#88c0d0;">latLng</span><span>(position</span><span style="color:#81a1c1;">.</span><span>coords</span><span style="color:#81a1c1;">.</span><span>latitude</span><span style="color:#eceff4;">, </span><span>position</span><span style="color:#81a1c1;">.</span><span>coords</span><span style="color:#81a1c1;">.</span><span>longitude))
</span><span> })
</span><span>}
</span></code></pre>
<p>If we run the current code, nothing will be displayed on our map as we need to specify a tile provider first. We will be using Open Street Map</p>
<pre data-lang="javascript" style="background-color:#2e3440;color:#d8dee9;" class="language-javascript "><code class="language-javascript" data-lang="javascript"><span style="color:#81a1c1;">L.</span><span style="color:#88c0d0;">tileLayer</span><span>(</span><span style="color:#a3be8c;">'http://{s}.tile.osm.org/{z}/{x}/{y}.png'</span><span style="color:#eceff4;">, </span><span>{ attribution</span><span style="color:#eceff4;">: </span><span style="color:#a3be8c;">'OSM' </span><span>})</span><span style="color:#81a1c1;">.</span><span style="color:#88c0d0;">addTo</span><span>(map)
</span></code></pre>
<p><img src="/posts/4-display-geojson-data-using-flask-and-mongodb/Screenshot-from-2019-06-28-22-17-19.png" alt="Rendered maps using leaflet" /></p>
<p>Yay, our map is finally showing up! Let's fetch the data from that simple API endpoint we have made before and add those to our Leaflet map.</p>
<p>Let’s fetch the data from that simple API endpoint we have made before and add those to our Leaflet map. We can use a simple XMLHTTPRequest, but I prefer using axios, which is a simple HTTP client.</p>
<pre data-lang="html" style="background-color:#2e3440;color:#d8dee9;" class="language-html "><code class="language-html" data-lang="html"><span style="color:#81a1c1;"><script </span><span style="color:#8fbcbb;">src</span><span style="color:#eceff4;">=</span><span style="color:#a3be8c;">"https://unpkg.com/axios/dist/axios.min.js"</span><span style="color:#81a1c1;">></script>
</span></code></pre>
<p>We can fetch the data from our API endpoint using axios.get</p>
<pre data-lang="javascript" style="background-color:#2e3440;color:#d8dee9;" class="language-javascript "><code class="language-javascript" data-lang="javascript"><span>axios</span><span style="color:#81a1c1;">.</span><span style="color:#88c0d0;">get</span><span>(</span><span style="color:#a3be8c;">'http://127.0.0.1:5000/points'</span><span>)
</span><span> </span><span style="color:#81a1c1;">.</span><span style="color:#88c0d0;">then</span><span>(response </span><span style="color:#81a1c1;">=> </span><span>{
</span><span> </span><span style="color:#8fbcbb;">console</span><span style="color:#81a1c1;">.</span><span style="color:#88c0d0;">log</span><span>(response</span><span style="color:#81a1c1;">.</span><span>data)
</span><span> })
</span></code></pre>
<p>Leaflet supports creating a GeoJSON layer and display it on the map</p>
<pre data-lang="javascript" style="background-color:#2e3440;color:#d8dee9;" class="language-javascript "><code class="language-javascript" data-lang="javascript"><span style="color:#81a1c1;">L.</span><span style="color:#88c0d0;">geoJSON</span><span>(response</span><span style="color:#81a1c1;">.</span><span>data</span><span style="color:#eceff4;">, </span><span>{})</span><span style="color:#81a1c1;">.</span><span style="color:#88c0d0;">addTo</span><span>(map)
</span></code></pre>
<p>As you can see, the advantages of using Leaflet to display the data we have stored on our MongoDB pretty easy, as we won't need to change much in our code in the future if we want to display in kind of GeoJSON features.</p>
<h2 id="conclusion">Conclusion</h2>
<p>I wrote this article after doing some research on how to handle geospatial data and how can I display them pretty easily on a project I'm working on. You should by now have an idea of how to</p>
<ul>
<li>
<p>Get started with simple Flask web pages</p>
</li>
<li>
<p>GeoJSON, what's and how to store the data in a proper database like MongoDB</p>
</li>
<li>
<p>How to fetch and display the data using axios and Leaflet.</p>
</li>
</ul>
<p>You can find the final code here with a few tweaks to allow showing whatever type of GeoJSON features.</p>
<pre data-lang="python" style="background-color:#2e3440;color:#d8dee9;" class="language-python "><code class="language-python" data-lang="python"><span style="color:#81a1c1;">from </span><span>flask </span><span style="color:#81a1c1;">import </span><span>Flask</span><span style="color:#eceff4;">, </span><span>render_template</span><span style="color:#eceff4;">, </span><span>jsonify
</span><span>
</span><span>app </span><span style="color:#81a1c1;">= </span><span style="color:#88c0d0;">Flask</span><span>(__name__)
</span><span style="color:#81a1c1;">from </span><span>pymongo </span><span style="color:#81a1c1;">import </span><span>MongoClient
</span><span>
</span><span>client </span><span style="color:#81a1c1;">= </span><span style="color:#88c0d0;">MongoClient</span><span>()
</span><span>db </span><span style="color:#81a1c1;">= </span><span>client</span><span style="color:#81a1c1;">.</span><span>geojson_flask
</span><span>geodata_collection </span><span style="color:#81a1c1;">= </span><span>db</span><span style="color:#81a1c1;">.</span><span>geodata
</span><span>
</span><span style="color:#d08770;">@</span><span>app</span><span style="color:#81a1c1;">.</span><span>route(</span><span style="color:#a3be8c;">'/geojson-features'</span><span style="color:#eceff4;">, </span><span>methods</span><span style="color:#81a1c1;">=</span><span>[</span><span style="color:#a3be8c;">'GET'</span><span>])
</span><span style="color:#81a1c1;">def </span><span style="color:#88c0d0;">get_all_points</span><span>():
</span><span> features </span><span style="color:#81a1c1;">= </span><span>[]
</span><span> </span><span style="color:#81a1c1;">for </span><span>geo_feature </span><span style="color:#81a1c1;">in </span><span>geodata_collection</span><span style="color:#81a1c1;">.</span><span style="color:#88c0d0;">find</span><span>({}):
</span><span> features</span><span style="color:#81a1c1;">.</span><span style="color:#88c0d0;">append</span><span>({
</span><span> </span><span style="color:#a3be8c;">"type"</span><span style="color:#eceff4;">: </span><span style="color:#a3be8c;">"Feature"</span><span style="color:#eceff4;">,
</span><span> </span><span style="color:#a3be8c;">"geometry"</span><span style="color:#eceff4;">: </span><span>{
</span><span> </span><span style="color:#a3be8c;">"type"</span><span style="color:#eceff4;">: </span><span>geo_feature[</span><span style="color:#a3be8c;">'geometry'</span><span>][</span><span style="color:#a3be8c;">'type'</span><span>]</span><span style="color:#eceff4;">,
</span><span> </span><span style="color:#a3be8c;">"coordinates"</span><span style="color:#eceff4;">: </span><span>geo_feature[</span><span style="color:#a3be8c;">'geometry'</span><span>][</span><span style="color:#a3be8c;">'coordinates'</span><span>]
</span><span> }
</span><span> })
</span><span> </span><span style="color:#81a1c1;">return </span><span style="color:#88c0d0;">jsonify</span><span>(features)
</span><span>
</span><span style="color:#d08770;">@</span><span>app</span><span style="color:#81a1c1;">.</span><span>route(</span><span style="color:#a3be8c;">'/'</span><span>)
</span><span style="color:#81a1c1;">def </span><span style="color:#88c0d0;">main</span><span>():
</span><span> </span><span style="color:#81a1c1;">return </span><span style="color:#88c0d0;">render_template</span><span>(</span><span style="color:#a3be8c;">'main.html'</span><span>)
</span><span>
</span><span>app</span><span style="color:#81a1c1;">.</span><span style="color:#88c0d0;">run</span><span>()
</span></code></pre>
<pre data-lang="html" style="background-color:#2e3440;color:#d8dee9;" class="language-html "><code class="language-html" data-lang="html"><span style="color:#81a1c1;"><!</span><span style="color:#eceff4;">DOCTYPE </span><span style="color:#81a1c1;">html>
</span><span style="color:#eceff4;">
</span><span style="color:#81a1c1;"><head>
</span><span style="color:#eceff4;"> </span><span style="color:#81a1c1;"><title></span><span style="color:#eceff4;">GeoJSON + Flask + MongoDB</span><span style="color:#81a1c1;"></title>
</span><span style="color:#eceff4;"> </span><span style="color:#81a1c1;"><meta </span><span style="color:#8fbcbb;">charset</span><span style="color:#eceff4;">=</span><span style="color:#a3be8c;">"utf-8"</span><span style="color:#81a1c1;">>
</span><span style="color:#eceff4;"> </span><span style="color:#81a1c1;"><link </span><span style="color:#8fbcbb;">rel</span><span style="color:#eceff4;">=</span><span style="color:#a3be8c;">"stylesheet" </span><span style="color:#8fbcbb;">href</span><span style="color:#eceff4;">=</span><span style="color:#a3be8c;">"https://unpkg.com/leaflet@1.5.1/dist/leaflet.css" </span><span style="color:#81a1c1;">/>
</span><span style="color:#eceff4;"> </span><span style="color:#81a1c1;"><script </span><span style="color:#8fbcbb;">src</span><span style="color:#eceff4;">=</span><span style="color:#a3be8c;">"https://unpkg.com/leaflet@1.5.1/dist/leaflet.js"</span><span style="color:#81a1c1;">></script>
</span><span style="color:#eceff4;"> </span><span style="color:#81a1c1;"><script </span><span style="color:#8fbcbb;">src</span><span style="color:#eceff4;">=</span><span style="color:#a3be8c;">"https://unpkg.com/axios/dist/axios.min.js"</span><span style="color:#81a1c1;">></script>
</span><span style="color:#81a1c1;"></head>
</span><span style="color:#eceff4;">
</span><span style="color:#81a1c1;"><body>
</span><span style="color:#eceff4;"> </span><span style="color:#81a1c1;"><h1>
</span><span style="color:#eceff4;"> Let's display some nice maps here!
</span><span style="color:#eceff4;"> </span><span style="color:#81a1c1;"></h1>
</span><span style="color:#eceff4;"> </span><span style="color:#81a1c1;"><div </span><span style="color:#8fbcbb;">id</span><span style="color:#eceff4;">=</span><span style="color:#a3be8c;">"map" </span><span style="color:#8fbcbb;">style</span><span style="color:#eceff4;">=</span><span style="color:#a3be8c;">"</span><span>height</span><span style="color:#eceff4;">: </span><span style="color:#b48ead;">80</span><span style="color:#81a1c1;">vh</span><span style="color:#eceff4;">;</span><span style="color:#a3be8c;">"</span><span style="color:#81a1c1;">></div>
</span><span style="color:#eceff4;">
</span><span style="color:#eceff4;"> </span><span style="color:#81a1c1;"><script>
</span><span style="color:#eceff4;">
</span><span style="color:#eceff4;"> </span><span style="color:#81a1c1;">var </span><span>map </span><span style="color:#81a1c1;">= </span><span style="color:#8fbcbb;">L</span><span style="color:#81a1c1;">.</span><span style="color:#88c0d0;">map</span><span style="color:#eceff4;">(</span><span style="color:#a3be8c;">'map'</span><span style="color:#eceff4;">)</span><span style="color:#81a1c1;">.</span><span style="color:#88c0d0;">setView</span><span style="color:#eceff4;">([</span><span style="color:#b48ead;">51</span><span style="color:#eceff4;">.</span><span style="color:#b48ead;">505</span><span style="color:#eceff4;">, </span><span style="color:#81a1c1;">-</span><span style="color:#b48ead;">0</span><span style="color:#eceff4;">.</span><span style="color:#b48ead;">09</span><span style="color:#eceff4;">], </span><span style="color:#b48ead;">13</span><span style="color:#eceff4;">)
</span><span style="color:#eceff4;"> </span><span style="color:#81a1c1;">if </span><span style="color:#eceff4;">(</span><span style="color:#8fbcbb;">navigator</span><span style="color:#81a1c1;">.</span><span style="color:#eceff4;">geolocation) {
</span><span style="color:#eceff4;"> </span><span style="color:#8fbcbb;">navigator</span><span style="color:#81a1c1;">.</span><span style="color:#eceff4;">geolocation</span><span style="color:#81a1c1;">.</span><span style="color:#88c0d0;">getCurrentPosition</span><span style="color:#eceff4;">((</span><span>position</span><span style="color:#eceff4;">) </span><span style="color:#81a1c1;">=> </span><span style="color:#eceff4;">{
</span><span style="color:#eceff4;"> </span><span>map</span><span style="color:#81a1c1;">.</span><span style="color:#88c0d0;">panTo</span><span style="color:#eceff4;">(</span><span style="color:#8fbcbb;">L</span><span style="color:#81a1c1;">.</span><span style="color:#88c0d0;">latLng</span><span style="color:#eceff4;">(</span><span>position</span><span style="color:#81a1c1;">.</span><span style="color:#eceff4;">coords</span><span style="color:#81a1c1;">.</span><span style="color:#eceff4;">latitude, </span><span>position</span><span style="color:#81a1c1;">.</span><span style="color:#eceff4;">coords</span><span style="color:#81a1c1;">.</span><span style="color:#eceff4;">longitude))
</span><span style="color:#eceff4;"> })
</span><span style="color:#eceff4;"> }
</span><span style="color:#eceff4;"> </span><span style="color:#8fbcbb;">L</span><span style="color:#81a1c1;">.</span><span style="color:#88c0d0;">tileLayer</span><span style="color:#eceff4;">(</span><span style="color:#a3be8c;">'http://{s}.tile.osm.org/{z}/{x}/{y}.png'</span><span style="color:#eceff4;">, {
</span><span style="color:#eceff4;"> attribution: </span><span style="color:#a3be8c;">'Open street map'
</span><span style="color:#eceff4;"> })</span><span style="color:#81a1c1;">.</span><span style="color:#88c0d0;">addTo</span><span style="color:#eceff4;">(</span><span>map</span><span style="color:#eceff4;">)
</span><span style="color:#eceff4;">
</span><span style="color:#eceff4;"> </span><span>axios</span><span style="color:#81a1c1;">.</span><span style="color:#88c0d0;">get</span><span style="color:#eceff4;">(</span><span style="color:#a3be8c;">'http://127.0.0.1:5000/geojson-features'</span><span style="color:#eceff4;">)
</span><span style="color:#eceff4;"> </span><span style="color:#81a1c1;">.</span><span style="color:#88c0d0;">then</span><span style="color:#eceff4;">(</span><span>response </span><span style="color:#81a1c1;">=> </span><span style="color:#eceff4;">{
</span><span style="color:#eceff4;"> </span><span style="color:#8fbcbb;">console</span><span style="color:#81a1c1;">.</span><span style="color:#88c0d0;">log</span><span style="color:#eceff4;">(</span><span>response</span><span style="color:#81a1c1;">.</span><span style="color:#eceff4;">data)
</span><span style="color:#eceff4;"> </span><span style="color:#8fbcbb;">L</span><span style="color:#81a1c1;">.</span><span style="color:#88c0d0;">geoJSON</span><span style="color:#eceff4;">(</span><span>response</span><span style="color:#81a1c1;">.</span><span style="color:#eceff4;">data, {})</span><span style="color:#81a1c1;">.</span><span style="color:#88c0d0;">addTo</span><span style="color:#eceff4;">(</span><span>map</span><span style="color:#eceff4;">);
</span><span style="color:#eceff4;">
</span><span style="color:#eceff4;"> })
</span><span style="color:#eceff4;"> </span><span style="color:#81a1c1;"></script>
</span><span style="color:#eceff4;">
</span><span style="color:#81a1c1;"></body>
</span><span style="color:#81a1c1;"></html>
</span></code></pre>
Authenticator 3.32 released!2019-05-22T00:00:00+00:002019-05-22T00:00:00+00:00
Unknown
https://belmoussaoui.com/blog/4-authenticator-3-32-released/<p>I have been planning to work a bit on <a rel="nofollow noreferrer" href="https://flathub.org/apps/details/com.github.bilelmoussaoui.Authenticator">Authenticator</a> and push a new release on Flathub for a while, it took a long time, the latest release was 0.2.5 released the 11 September 2018. I will be following the GNOME release schedule and versioning to make it easier for the users to track the latest release of Authenticator easily, that's why the release number bumped from 0.2.5 to 3.32.</p>
<h2 id="what-s-authenticator">What's Authenticator?</h2>
<p>Authenticator is a two-factor authentication application which basically generates a one time usage pin based on a unique token. It's made for GNOME using Python and GTK. The source code is available here <a rel="nofollow noreferrer" href="https://gitlab.gnome.org/World/Authenticator">https://gitlab.gnome.org/World/Authenticator</a></p>
<figure>
<img src="/posts/4-authenticator-3-32-released/screenshot3.png" >
<figcaption>Authenticator - Main Window</figcaption>
</figure><h2 id="what-s-new-in-this-release">What's new in this release?</h2>
<p>I have worked mostly on re-factoring some parts of the code (still some work need to be done), but here's what we get for this new release:</p>
<ul>
<li>
<p>The possibility to lock your application with a password before having access to the accounts, the settings window, backup and restore functionalities</p>
</li>
<li>
<p>GNOME Shell Search Provider: you can now type the account name or the provider of that account on your GNOME Shell Search bar and get the results of what's stored on Authenticator if the authentication with a password is disabled, you can then just click on the account and the PIN is copied to your clipboard</p>
</li>
<li>
<p>We now fetch an icon for each provider you use by downloading the highest resolution favicon we can find on their website, if we can't find one, you can set an image or leave it empty</p>
</li>
<li>
<p>Night Light: we now detect when the night light feature is enabled on your GNOME system and we switch automatically to a dark theme, you can enable that from the settings window</p>
</li>
<li>
<p>The possibility to add an account for a provider (website/application) that's not available on our pre-shipped database</p>
</li>
<li>
<p>A fancy new settings window</p>
</li>
<li>
<p>Keyboard shortcuts</p>
</li>
<li>
<p>A bunch of bug fixes</p>
</li>
</ul>
<p><img src="/posts/4-authenticator-3-32-released/screenshot1.png" alt="Empty Window" /></p>
<p><img src="/posts/4-authenticator-3-32-released/screenshot2.png" alt="Main Window" /></p>
<p><img src="/posts/4-authenticator-3-32-released/screenshot3.png" alt="Add an account window" /></p>
<p><img src="/posts/4-authenticator-3-32-released/screenshot4.png" alt="Preferences" /></p>
<h2 id="grab-it-now-from-flathub">Grab it now from Flathub</h2>
<p>You can already download or update the application to the latest release from Flathub. If you are already a user of Authenticator, please back up your data, remove the current version and install the latest one, this is mostly due to the code refactoring I have done to make few things possible, you can restore your data later once the update is installed.</p>
<p><a rel="nofollow noreferrer" href="https://flathub.org/apps/details/com.github.bilelmoussaoui.Authenticator">Download from Flathub</a></p>
<h3 id="authenticator-beta-and-nightlies">Authenticator Beta and Nightlies</h3>
<p>Authenticator is also available on Flathub Beta to test the latest changes before they land on the stable release. You can install Authenticator from there using</p>
<pre data-lang="bash" style="background-color:#2e3440;color:#d8dee9;" class="language-bash "><code class="language-bash" data-lang="bash"><span style="color:#88c0d0;">flatpak</span><span> install flathub-beta com.github.bilelmoussaoui.Authenticator
</span></code></pre>
<p>We also offer nightlies flatapk's which are built for each commit on our Gitlab repository.</p>
<h2 id="what-are-the-plans-for-the-next-release">What are the plans for the next release?</h2>
<p>You can find in our issues tracker the list of features we would like to implement, here's a small list of what you might expect in the next release</p>
<ul>
<li>
<p>Support for Steam Authenticator</p>
</li>
<li>
<p>Support taking a photo of the QRCode</p>
</li>
<li>
<p>Use Flatpak's Screenshot portal instead of GNOME shell's one</p>
</li>
</ul>
<h2 id="how-to-contribute">How to contribute</h2>
<p>You can help me improve Authenticator by either contributing code, here is a simple <a rel="nofollow noreferrer" href="https://gitlab.gnome.org/World/Authenticator/blob/master/CONTRIBUTING.md">contribution guideline</a>. If you're a native English speaker, you can help us improve the wordings we are currently using, or you can help us translate Authenticator to your language <a rel="nofollow noreferrer" href="https://l10n.gnome.org/module/authenticator/">here</a>.</p>
<p>List of useful links:</p>
<ul>
<li>Source code: <a rel="nofollow noreferrer" href="https://gitlab.gnome.org/World/Authenticator/">https://gitlab.gnome.org/World/Authenticator/</a></li>
<li>Translate Authenticator: <a rel="nofollow noreferrer" href="https://l10n.gnome.org/module/authenticator/">https://l10n.gnome.org/module/authenticator/</a></li>
<li>Matrix chatroom: <a rel="nofollow noreferrer" href="https://riot.im/app/#/room/#authenticator:matrix.org">https://riot.im/app/#/room/#authenticator:matrix.org</a></li>
</ul>
How to create a GTK application using Python – Part 12019-05-13T00:00:00+00:002019-05-13T00:00:00+00:00
Unknown
https://belmoussaoui.com/blog/3-how-to-create-a-gtk-application-using-python-part-1/<p>Hello everyone, this new article is a start of a new era on my blog as I will start writing few articles to share some tips or like on this one, a series of articles to build something together from scratch.</p>
<p>Writing a GUI(graphical user interface) application using Python isn't something new as you can already use a long list of libraries that allow you doing that. The difference from what are we going to do here is that we are going to use a list of GNOME libraries like GTK. Once the application is usable, we are going to add Meson as a build system and ship it on Flathub as a Flatpak. We will also talk a bit about application integration by following Freedesktop standards like shipping a metainfo file that describes the application, a desktop file, beautiful icons that follows GNOME design guidelines.</p>
<p>The most simple use case is a simple Todo list that should allow us to add new tasks to a list, remove them, mark them as done/undone, search on the tasks list, we will be adding features with time like using Handy (libhandy) to make the application mobile ready.</p>
<h2 id="requirements">Requirements</h2>
<p>We will need a few things in order to start hacking, the first thing is installing GNOME Builder. It's the IDE we will be using, it integrates a few useful features that we will be using like one click to build, run the application or the possibility to change the UI of our application easily as it integrates a part of Glade.</p>
<h3 id="install-gnome-builder">Install GNOME Builder</h3>
<p><img src="/posts/3-how-to-create-a-gtk-application-using-python-part-1/org.gnome.Builder.png" alt="GNOME Builder" /></p>
<p>You won't be needing to install anything else. Every library that we might need like GTK, python-gobject, gobject-introspection, flatpak-builder, Meson are already shipped within Freedesktop/GNOME Runtime. We will be using the latest stable GNOME Runtime 3.32 which should be already installed on your system if you have installed Builder from Flathub (I do recommend you to do that instead of using the "normal" packaging formats). Otherwise, you can get that by typing in Terminal:</p>
<p><img src="/posts/3-how-to-create-a-gtk-application-using-python-part-1/Screenshot-from-2019-05-13-18-33-20.png" alt="Install GNOME SDK" /></p>
<h2 id="getting-started">Getting started</h2>
<p>Let's fire up GNOME Builder and create a new project. Once Builder's main window is shown, you can see a "Start a new project" button</p>
<figure>
<img src="/posts/3-how-to-create-a-gtk-application-using-python-part-1/Screenshot-from-2019-05-13-18-37-32.png" >
<figcaption>GNOME Builder - Main Window</figcaption>
</figure>
<p>Once we have clicked on that, we get a list of different languages and projects types to choose from. We are going to select Python and GNOME Application as a template, we also need to choose a name for our project and an application ID. The Application ID should follow the reverse domain name standards, you can read more about that here on the GNOME Wiki</p>
<figure>
<img src="/posts/3-how-to-create-a-gtk-application-using-python-part-1/Screenshot-from-2019-05-13-18-41-39.png" >
<figcaption>GNOME Builder - Project configuration</figcaption>
</figure>
<p>Before going through the list of files generated by Builder, we can check the result of what we have done so far by clicking on the run button or using CTRL + F5 shortcut.</p>
<figure>
<img src="/posts/3-how-to-create-a-gtk-application-using-python-part-1/Screenshot-from-2019-05-13-19-19-21.png" >
<figcaption>GNOME Builder - Header bar</figcaption>
</figure>
<p>Builder will use the Flatpak manifest generated automatically to build and run the application, you can see the build progress by clicking on "Build output" tab</p>
<figure>
<img src="/posts/3-how-to-create-a-gtk-application-using-python-part-1/Screenshot-from-2019-05-13-19-23-54.png" >
<figcaption>GNOME Builder - Build output of Todo</figcaption>
</figure>
<p>The build shouldn't take a long time (few seconds) and the current application should look like this</p>
<figure>
<img src="/posts/3-how-to-create-a-gtk-application-using-python-part-1/Screenshot-from-2019-05-13-19-26-40.png" >
<figcaption>GNOME Builder - GNOME Application Template for Python</figcaption>
</figure>
<p>Now that we are sure that everything runs correctly, let's take a look at the list of files that were generated by Builder.</p>
<pre data-lang="bash" style="background-color:#2e3440;color:#d8dee9;" class="language-bash "><code class="language-bash" data-lang="bash"><span style="color:#88c0d0;">belmoussaoui@localhost </span><span style="color:#81a1c1;">~</span><span>/P/Todo </span><span style="color:#81a1c1;">></span><span> .
</span><span style="color:#88c0d0;">├──</span><span> build-aux
</span><span style="color:#88c0d0;">│</span><span> └── meson
</span><span style="color:#88c0d0;">│</span><span> └── postinstall.py
</span><span style="color:#88c0d0;">├──</span><span> com.belmoussaoui.Todo.json
</span><span style="color:#88c0d0;">├──</span><span> COPYING
</span><span style="color:#88c0d0;">├──</span><span> data
</span><span style="color:#88c0d0;">│</span><span> ├── com.belmoussaoui.Todo.appdata.xml.in
</span><span style="color:#88c0d0;">│</span><span> ├── com.belmoussaoui.Todo.desktop.in
</span><span style="color:#88c0d0;">│</span><span> ├── com.belmoussaoui.Todo.gschema.xml
</span><span style="color:#88c0d0;">│</span><span> └── meson.build
</span><span style="color:#88c0d0;">├──</span><span> meson.build
</span><span style="color:#88c0d0;">├──</span><span> po
</span><span style="color:#88c0d0;">│</span><span> ├── LINGUAS
</span><span style="color:#88c0d0;">│</span><span> ├── meson.build
</span><span style="color:#88c0d0;">│</span><span> └── POTFILES
</span><span style="color:#88c0d0;">└──</span><span> src
</span><span> </span><span style="color:#88c0d0;">├──</span><span> __init__.py
</span><span> </span><span style="color:#88c0d0;">├──</span><span> main.py
</span><span> </span><span style="color:#88c0d0;">├──</span><span> meson.build
</span><span> </span><span style="color:#88c0d0;">├──</span><span> todo.gresource.xml
</span><span> </span><span style="color:#88c0d0;">├──</span><span> todo.in
</span><span> </span><span style="color:#88c0d0;">├──</span><span> window.py
</span><span> </span><span style="color:#88c0d0;">└──</span><span> window.ui
</span><span>
</span><span style="color:#88c0d0;">5</span><span> directories, 18 files
</span></code></pre>
<p>What does interest us for now is the source code of the application, which is under src directory.</p>
<ul>
<li>
<p><code>main.py</code>: contains an Application which is a subclass of <code>Gtk.Application</code>, it allows us to set the application id that we have defined during the creation of the project and other attributes of an application. It also contains a <code>do_activate</code> function which is the function that will be executed once the application was activated.</p>
</li>
<li>
<p><code>meson.build</code>: contains where those python files should be installed and how the gresource file should be built and installed</p>
</li>
<li>
<p><code>todo.gresource.xml</code>: it's an XML file that defines the resources that our application needs like images, CSS files and UI files.</p>
</li>
<li>
<p><code>todo.in</code>: is the file that will be installed under <code>$PREFIX/bin</code>, which is the path on your system where all the binaries get installed. It's a Python file that has a <code>.in</code> extension which means it's a file that will be configured during the build as we need to pass to our application where we have installed our resources, where we can find the translations files for our application and other configurations we might need.</p>
</li>
<li>
<p><code>window.py</code>: is the main window widget, which is a subclass of <code>Gtk.ApplicationWindow</code></p>
</li>
<li>
<p><code>window.ui</code>: is a file that defines which widgets our main window contains, it can be opened using Glade or GNOME Builder. You can edit the file either manually by writing the XML tags to define your widgets or just drag and drop your widgets if you open that file using Glade.</p>
</li>
</ul>
<h3 id="the-ui-of-the-main-window">The UI of the main window</h3>
<p>Now that we know where the widgets used in the main window are defined we can change those in order to fit our project. If we open the <code>window.ui</code> file in Builder, it opens the file in the design view, you can switch to the source view if you want to change the file manually.</p>
<figure>
<img src="/posts/3-how-to-create-a-gtk-application-using-python-part-1/Screenshot-from-2019-05-14-11-51-14.png" >
<figcaption>GNOME Builder - UI files designer</figcaption>
</figure>
<p>Builder allow you to easily figure out which GTK Widgets are used in our UI file.</p>
<figure>
<img src="/posts/3-how-to-create-a-gtk-application-using-python-part-1/Screenshot-from-2019-05-14-11-54-23.png" >
<figcaption>GNOME Builder - UI Widgets in the default GNOME application template</figcaption>
</figure>
<p>What we would like to do is to change the "Hello World" label to something else. The widget that fits our needs here is a <code>ListBox</code> which is basically a vertically oriented container that we can fill with any widget we want. You can right click on any widget and click on delete, so let's remove the label widget and add a <code>GtkListBox</code> instead.</p>
<p><img src="/posts/3-how-to-create-a-gtk-application-using-python-part-1/Screenshot-from-2019-05-14-12-00-15.png" alt="Widgets selection" /></p>
<p>We can change the attributes of the selected widget from the right panel. The only thing we will be changing for now is the ID which is the identifier that will allow us to access and change the widget manually from our <code>window.py</code> file.</p>
<p><img src="/posts/3-how-to-create-a-gtk-application-using-python-part-1/Screenshot-from-2019-05-14-12-06-31.png" alt="Widget Properties" /></p>
<p>As we are using <code>Gtk.Template</code> which basically let you inherit a widget in your class while keeping the widgets, their attributes and how they should behave defined in the UI file.</p>
<pre data-lang="python" style="background-color:#2e3440;color:#d8dee9;" class="language-python "><code class="language-python" data-lang="python"><span style="color:#81a1c1;">from </span><span>gi</span><span style="color:#81a1c1;">.</span><span>repository </span><span style="color:#81a1c1;">import </span><span>Gtk
</span><span>
</span><span style="color:#d08770;">@</span><span>Gtk</span><span style="color:#81a1c1;">.</span><span>Template(resource_path</span><span style="color:#81a1c1;">=</span><span style="color:#a3be8c;">'/com/belmoussaoui/Todo/window.ui'</span><span>)
</span><span style="color:#81a1c1;">class </span><span style="color:#8fbcbb;">TodoWindow</span><span>(</span><span style="color:#8fbcbb;">Gtk</span><span style="color:#81a1c1;">.</span><span style="color:#8fbcbb;">ApplicationWindow</span><span>):
</span><span> __gtype_name__ </span><span style="color:#81a1c1;">= </span><span style="color:#a3be8c;">'TodoWindow'
</span><span>
</span><span> label </span><span style="color:#81a1c1;">= </span><span>Gtk</span><span style="color:#81a1c1;">.</span><span>Template</span><span style="color:#81a1c1;">.</span><span style="color:#88c0d0;">Child</span><span>()
</span><span>
</span><span> </span><span style="color:#81a1c1;">def </span><span style="color:#88c0d0;">__init__</span><span>(self</span><span style="color:#eceff4;">, </span><span style="color:#81a1c1;">**</span><span>kwargs):
</span><span> </span><span style="font-style:italic;color:#88c0d0;">super</span><span>()</span><span style="color:#81a1c1;">.</span><span style="color:#88c0d0;">__init__</span><span>(</span><span style="color:#81a1c1;">**</span><span>kwargs)
</span></code></pre>
<p>If we look closely to the content of the <code>window.py</code> we can see that we are setting the Template <code>resource_path</code> using a <code>Gtk.Template</code> as a decorator. We need also to set a <code>__gtype_name__</code> to define the name the composite widget we are going to use from the UI file.</p>
<p><img src="/posts/3-how-to-create-a-gtk-application-using-python-part-1/Screenshot-from-2019-05-14-12-16-39.png" alt="Widget Name" /></p>
<p>For every child of our TodoWindow widget that we would like to have access to, all we need to do is to add a new property to our class, the name of the property must be the ID we have given to the widget and it should be a <code>Gtk.Template.Child</code>.</p>
<p>The reason why we would like to access to the <code>todo_list</code> widget is we will be reading the different tasks from a database and we would like to fill the todo list from that.</p>
<p>Let's add a bunch of random tasks manually to see how things look like. We are going to use a GtkLabel for now and update that later to use a widget that we are going to create ourselves.</p>
<pre data-lang="python" style="background-color:#2e3440;color:#d8dee9;" class="language-python "><code class="language-python" data-lang="python"><span style="color:#81a1c1;">from </span><span>gi</span><span style="color:#81a1c1;">.</span><span>repository </span><span style="color:#81a1c1;">import </span><span>Gtk
</span><span>
</span><span style="color:#d08770;">@</span><span>Gtk</span><span style="color:#81a1c1;">.</span><span>Template(resource_path</span><span style="color:#81a1c1;">=</span><span style="color:#a3be8c;">'/com/belmoussaoui/Todo/window.ui'</span><span>)
</span><span style="color:#81a1c1;">class </span><span style="color:#8fbcbb;">TodoWindow</span><span>(</span><span style="color:#8fbcbb;">Gtk</span><span style="color:#81a1c1;">.</span><span style="color:#8fbcbb;">ApplicationWindow</span><span>):
</span><span> __gtype_name__ </span><span style="color:#81a1c1;">= </span><span style="color:#a3be8c;">'TodoWindow'
</span><span>
</span><span> todo_list </span><span style="color:#81a1c1;">= </span><span>Gtk</span><span style="color:#81a1c1;">.</span><span>Template</span><span style="color:#81a1c1;">.</span><span style="color:#88c0d0;">Child</span><span>()
</span><span>
</span><span> </span><span style="color:#81a1c1;">def </span><span style="color:#88c0d0;">__init__</span><span>(self</span><span style="color:#eceff4;">, </span><span style="color:#81a1c1;">**</span><span>kwargs):
</span><span> </span><span style="font-style:italic;color:#88c0d0;">super</span><span>()</span><span style="color:#81a1c1;">.</span><span style="color:#88c0d0;">__init__</span><span>(</span><span style="color:#81a1c1;">**</span><span>kwargs)
</span><span> </span><span style="color:#81a1c1;">self.</span><span style="color:#88c0d0;">__fill_todo_list</span><span>()
</span><span>
</span><span> </span><span style="color:#81a1c1;">def </span><span style="color:#88c0d0;">__fill_todo_list</span><span>(self):
</span><span> tasks </span><span style="color:#81a1c1;">= </span><span>[</span><span style="color:#a3be8c;">"Finish this article"</span><span style="color:#eceff4;">,
</span><span> </span><span style="color:#a3be8c;">"Finish the Rust Book"</span><span style="color:#eceff4;">,
</span><span> </span><span style="color:#a3be8c;">"Get a new release of Authenticator"</span><span>]
</span><span> </span><span style="color:#81a1c1;">for </span><span>todo_item </span><span style="color:#81a1c1;">in </span><span>tasks:
</span><span> todo_label </span><span style="color:#81a1c1;">= </span><span>Gtk</span><span style="color:#81a1c1;">.</span><span style="color:#88c0d0;">Label</span><span>(todo_item)
</span><span> todo_label</span><span style="color:#81a1c1;">.</span><span style="color:#88c0d0;">show</span><span>()
</span><span> </span><span style="color:#81a1c1;">self.</span><span>todo_list</span><span style="color:#81a1c1;">.</span><span style="color:#88c0d0;">add</span><span>(todo_label)
</span></code></pre>
<figure>
<img src="/posts/3-how-to-create-a-gtk-application-using-python-part-1/Screenshot-from-2019-05-14-12-30-33.png" >
<figcaption>Todo - First screenshot</figcaption>
</figure><h2 id="conclusion">Conclusion</h2>
<p>When I started learning GTK a few years ago, you had to deal with installing the required dependencies, learn what' a build system and how to use one (we didn't even have Meson :/). Things got easier for newcomers, as you have seen on this first tutorial you can just start a new project and start hacking. Thank you, Builder, Flatpak, Meson, GTK and the maintainers of python-gobject for implementing GtkTemplate support. In the next tutorial, we will see how to create a widget that represents a task and how to store/read the tasks from a database. Till then here's a list of useful documentation to learn from:</p>
<ul>
<li>
<p>PyGObject API Reference <a rel="nofollow noreferrer" href="https://lazka.github.io/pgi-docs/">https://lazka.github.io/pgi-docs/</a></p>
</li>
<li>
<p>The Python GTK+ 3 Tutorial <a rel="nofollow noreferrer" href="https://python-gtk-3-tutorial.readthedocs.io/en/latest/index.html">https://python-gtk-3-tutorial.readthedocs.io/en/latest/index.html</a></p>
</li>
<li>
<p>Tutorial for beginners (Python) <a rel="nofollow noreferrer" href="https://developer.gnome.org/documentation/tutorials/beginners/getting_started.html">https://developer.gnome.org/documentation/tutorials/beginners/getting_started.html</a></p>
</li>
<li>
<p>Flatpak Documentation <a rel="nofollow noreferrer" href="http://docs.flatpak.org/en/latest/">http://docs.flatpak.org/en/latest/</a></p>
</li>
<li>
<p>Meson Documentation <a rel="nofollow noreferrer" href="https://mesonbuild.com/">https://mesonbuild.com/</a></p>
</li>
</ul>
AppData Initiative: the road towards 3.342019-05-04T00:00:00+00:002019-05-04T00:00:00+00:00
Unknown
https://belmoussaoui.com/blog/2-appdata-initiative-the-road-towards-3-34/<p>Hey! Don't know if you still remember about the AppData update initiative I started two months ago, in this article I will try to put some lights on the progress I have done so far.</p>
<p>So, things started with updating the AppData files of various GNOME apps, even third-party ones, in order to provide the best description of the applications on any software center out there. While doing that for some core apps, I got a bit bored as it's the same thing that should be done for almost every application:</p>
<ul>
<li>
<p>Check if the application is using the same application ID everywhere and uses that for the FreeDesktop files</p>
</li>
<li>
<p>Update the AppData file to the latest specs</p>
</li>
<li>
<p>Add tests to validate the AppData & Desktop files</p>
</li>
</ul>
<p>Then I checked some GNOME old classy games like Mines, Mahjongg and seen that a lot of them are still using that old build system that no one remembers in 2019, ehem, Autotools. I decided to give this games a bit of refresh to help newcomers to contribute pretty easily, I went ahead and created a simple Google Sheet with all the games and what needs to be done to improve the new contributor's life.</p>
<p><img src="/posts/2-appdata-initiative-the-road-towards-3-34/games-update.png" alt="Tracking sheet" /></p>
<p>As you can clearly see, there's a lot of green on that table, things were not the same in 3.30. I migrated a bunch of those projects to Meson, added a Flatpak manifest, a CI to build a Flatpak bundle to make the testing easier and allow the clone & hack work-flow that Builder + Flatpak allow us to do, updated the AppData of these games. Some of these changes were done by other contributors or the current maintainers of these games.</p>
<p>I also went ahead and did the same for some third-party applications I contribute to like Tilix, FeedReader...</p>
<h2 id="what-s-left-to-do">What's left to do?</h2>
<p>I'm hoping to get the whole applications core and third-party GNOME apps up to the new specs before 3.34, there's still a lot of apps that need to be either ported to Meson, have a Flatpak manifest, needs an updated AppData or just some new fresh beautiful screenshots. I try to keep the list here <a rel="nofollow noreferrer" href="https://gitlab.gnome.org/GNOME/Initiatives/issues/8">https://gitlab.gnome.org/GNOME/Initiatives/issues/8</a> up to date, if you ever want to help by improving the situation of an old application and you have no idea where to start from, you can always send me an email or PM on Twitter or Matrix.</p>
GNOME Initiative: AppData update2019-01-14T00:00:00+00:002019-01-14T00:00:00+00:00
Unknown
https://belmoussaoui.com/blog/1-gnome-initiative-appdata/<p>Hey everyone! Welcome to my first blog article on my brand new website. It has been a few months since I bought this domain name but I didn't have the opportunity to set a simple WordPress on it. I will try to keep it up to date and share various articles about stuff I love and enjoy during my free time.</p>
<h2 id="the-gnome-initiatives-what-s-it">The GNOME/initiatives, what's it?</h2>
<p>A small description from the Wiki page of the iniatives repository on GNOME's Gitlab instance: "Initiatives are cross-project goals that have wide acceptance inside the GNOME community as desired."</p>
<h2 id="what-s-the-appdata-update-initiative">What's the AppData update initiative?</h2>
<p>It's an initiative that I have started the day after I joined the GNOME Foundation as a member and I have been working days before that. It aims to provide an up to date AppData file, which is a file shipped with the application to provide information about the application the user is going to install like the screenshots, a description of the application, release change log and other details.</p>
<p>Those files were in some "unmaintained" projects out of date, had old screenshots or maybe an old format of the AppData file that doesn't follow the new <a rel="nofollow noreferrer" href="https://freedesktop.org/software/appstream/docs/chap-Quickstart.html">FreeDesktop specifications</a>. I started sending patches to fix those files on various of GNOME apps/games; I lost track of what I have already fixed and what still needs to be done, so I took the opportunity to start an initiative to provide up to date and validated AppData files.</p>
<h2 id="the-goals-of-the-appdata-update-initiative">The Goals of the AppData update initiative</h2>
<p>I have set three goals for each GNOME application:</p>
<ul>
<li>
<p>A unified application ID, which is basically the usage of the application ID as a filename for the AppData, Desktop, icons files shipped with the application itself. If we take Nautilus for example, it should ship an AppData file named <code>org.gnome.Nautilus.appdata.xml</code>, a desktop file named <code>org.gnome.Nautilus.desktop</code> and a bunch of icons prefixed by <code>org.gnome.Nautilus</code>.</p>
</li>
<li>
<p>An up to date AppData file: the goal here is to make sure we provide all the information the user might need on the AppData file, like the URL to report issues, to donate to GNOME, the latest release of the application and other informative, yet useful for users. Which are shown later by GNOME Software or other alternatives?</p>
</li>
<li>
<p>A validated AppData/Desktop files: The desktop file can be validated using a tool called <code>desktop-file-validate</code>, the same thing for the AppData file using <code>appstream-util</code>. The idea is to add two tests to the meson tests in order to always assure we are shipping a valid and up to date Desktop and AppData file.</p>
</li>
</ul>
<h2 id="how-things-are-going-on-so-far">How things are going on so far?</h2>
<p>As you can see in <a rel="nofollow noreferrer" href="https://gitlab.gnome.org/GNOME/Initiatives/issues/8">AppData update initiative</a> there's still a lot of work to be done, the list of application is not finished yet either. You can help by opening an issue or send a merge request to a project with an updated AppData file, tests to validate AppData/Desktop files or just pointing them out to me and will try to figure out some free time to deal with that.</p>
<p>If you want to help making better free and open source software a reality, start contributing now.</p>