<?xml version="1.0" encoding="utf-8"?>
<?xml-stylesheet type="text/xsl" href="../../assets/xml/rss.xsl" media="all"?><rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Mardy (Articoli su programmation)</title><link>http://mardy.it/</link><description></description><atom:link href="http://mardy.it/it/categories/programmation.xml" rel="self" type="application/rss+xml"></atom:link><language>it</language><copyright>Contents © 2026 &lt;a href="mailto:info@mardy.it"&gt;Alberto Mardegan&lt;/a&gt; </copyright><lastBuildDate>Sat, 11 Apr 2026 13:54:07 GMT</lastBuildDate><generator>Nikola (getnikola.com)</generator><docs>http://blogs.law.harvard.edu/tech/rss</docs><item><title>Teaser: connecting Nintendo Switch controllers to the Wii</title><link>http://mardy.it/it/blog/2026/04/teaser-connecting-nintendo-switch-controllers-to-the-wii.html</link><dc:creator>Alberto Mardegan</dc:creator><description>&lt;p&gt;A few months ago I started playing with the Bluetooth controller embedded in
the Nintendo Wii, to see if it was possible to get it to connect to other
devices than just the Wii remotes. If you ask internet forums, you might fall
into the impression that the Wii is not using a standard Bluetooth controller,
or that it has been somehow "crippled" in order to restrict it to connect to
the Wiimotes only, but that's not the case: it's an ordinary controller from
its era, it's just that the Wii software (both the official SDK and the
homebrew libogc) only uses it for the Wiimotes.&lt;/p&gt;
&lt;p&gt;I first started looking into
&lt;a href="https://github.com/devkitPro/libogc/tree/master/lwbt"&gt;&lt;code&gt;lwBT&lt;/code&gt;&lt;/a&gt;, the bluetooth
stack used in libogc, but I quickly realized that it was impossible to get full
control over the controller via this library: it has been adapted to work with
the subset of the HID protocol used by the Wiimotes, and even if you can issue
bluetooth commands to the controller via the HCI layer, you cannot receive the
replies, since they are already intercepted by lwBT and there's no way to hook
into them.&lt;/p&gt;
&lt;p&gt;So I started writing my own little bluetooth stack,
&lt;a href="https://github.com/embedded-game-controller/bt-embedded"&gt;&lt;code&gt;bt-embedded&lt;/code&gt;&lt;/a&gt; which
from the ground up has been designed to be used by embedded devices and
covering the use case of serving different clients running in a single process,
and I've integrated it into
&lt;a href="https://github.com/embedded-game-controller/embedded-game-controller"&gt;&lt;code&gt;embedded-game-controller&lt;/code&gt;&lt;/a&gt;
(though this is not merged into the &lt;code&gt;master&lt;/code&gt; branch yet) in order to allow
connecting to bluetooth controllers. And in the last couple of days I've
written a little ugly program to visualize the controllers in a 3D scene, in
order to better test them:&lt;/p&gt;
&lt;iframe src="https://vkvideo.ru/video_ext.php?oid=-230221529&amp;amp;id=456239018&amp;amp;hash=7f8047b16af7164f&amp;amp;hd=1" width="640" height="360" allow="autoplay; encrypted-media; fullscreen; picture-in-picture; screen-wake-lock;" frameborder="0" allowfullscreen&gt;&lt;/iframe&gt;

&lt;p&gt;Yes, the 3D model sucks, it was a nice model when I downloaded it from a free
site, but then heavily reduced the number of polygons using Blender and went a
bit too far; I'll do a better model later. The accelerometer information is not
shown yet (I'd like to use it to actually tilt the controller, instead of using
the joystick), but that will not be hard.&lt;/p&gt;
&lt;p&gt;At the moment, the only supported controllers are the Nintendo Switch Pro
controller (I'm using a clone here) and the Joy-cons, so there's still quite
some work to do to add more.&lt;/p&gt;
&lt;p&gt;It's been a journey in which I learned a lot about Bluetooth and USB and, who
knows, maybe this will come handy in the future too.&lt;/p&gt;</description><guid>http://mardy.it/it/blog/2026/04/teaser-connecting-nintendo-switch-controllers-to-the-wii.html</guid><pubDate>Sat, 11 Apr 2026 13:13:06 GMT</pubDate></item><item><title>No, libogc did not steal RTEMS code</title><link>http://mardy.it/it/blog/2025/04/no-libogc-did-not-steal-rtems-code.html</link><dc:creator>Alberto Mardegan</dc:creator><description>&lt;p&gt;&lt;em&gt;Update 09/05/2025:&lt;/em&gt; while the text below addresses a couple of specific blocks of code,
after publishing this article I've been contacted by some people providing more
evidence pointing that the similarity of the code goes beyond what one might
interpret as “inspiration”. There are also a couple of other bits on
information that the reader might want to take into account: first, a
&lt;a href="https://mas.to/@davejmurphy/114414723608693881"&gt;screenshot of an email&lt;/a&gt; in
which Shagkur tells his version of how the code came about (which seems to
point that, if a copyright infringement actually happened, this was done
without the knowledge of devkitPro's maintainers) and a &lt;a href="https://www.rtems.org/news/2025-05-06-rtems-devkit-libogc-response/"&gt;public statement by
RTEMS&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;All of a sudden the Wii homebrew community is in flames after Hector Martin
(&lt;a href="https://github.com/marcan"&gt;marcan&lt;/a&gt;, also known in the Linux Kernel
 community), co-author of “The Homebrew Channel” application, &lt;a href="https://github.com/fail0verflow/hbc"&gt;decided to close
the project&lt;/a&gt; and denounce libogc for its
“theft” of RTEMS's code:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;It has recently been revealed that the threading/OS implementation in libogc is, in fact, stolen from RTEMS. The authors of libogc didn't just steal proprietary Nintendo code, but also saw it fit to steal an open source RTOS and remove all attribution and copyright information. This goes far beyond ignorance about the copyright implications of reverse engineering Nintendo binaries, and goes straight into outright deliberate, malicious code theft and copyright infringement.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;A little below, Hector Martin provides this “proof”: &lt;em&gt;you can compare
&lt;a href="https://github.com/devkitPro/libogc/blob/52c525a13fd1762c10395c78875e3260f94368b5/libogc/lwp_threads.c#L580"&gt;this&lt;/a&gt;
function in libogc to
&lt;a href="https://github.com/atgreen/RTEMS/blob/2f200c7e642c214accb7cc6bd7f0f1784deec833/c/src/exec/score/src/thread.c#L385"&gt;this&lt;/a&gt;
function in a really old version of RTEMS&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;While I don't know the truth about libogc's history (I've started contributing
to it only in the last couple of years), and I'd welcome a first-hand
explanation from shagkur (Michael Wiedenbauer), I can confidently say that the
accusation is unfounded, by just looking at the code. To be more precise: I
cannot vouch for the whole of libogc, I can only say that this function, that
Hector Martin offers as example, actually hints that the code was not stolen.&lt;/p&gt;
&lt;p&gt;While it's obvious that the developer of libogc's code did have a look at
RTEMS's code, and this can be assumed because the variable names are very
similar (and in some cases they are so badly named, that they cannot be this
similar by accident!), the code only looks vaguely similar. Furthermore, for
some reason Hector Martin decided to pick a rather recent version of libogc's
code, but if we look at the &lt;a href="https://github.com/devkitPro/libogc/blob/e550333e6f2fc413fd42d6af9a9aaf036d9de9f6/libogc/lwp_threads.c#L469"&gt;first version that was
committed&lt;/a&gt;
(well, to git, at least), we see that similarities are much less striking.
Let's look at a small snippet of the function, where the thread object's
members are being initialized:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;RTEMS from 1996:&lt;/strong&gt;&lt;/p&gt;
&lt;div class="code"&gt;&lt;pre class="code literal-block"&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;the_thread&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;Start&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;is_preemptible&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;is_preemptible&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;the_thread&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;Start&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;is_timeslice&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;is_timeslice&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;the_thread&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;Start&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;isr_level&lt;/span&gt;&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;isr_level&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;the_thread&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;current_state&lt;/span&gt;&lt;span class="w"&gt;          &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;STATES_DORMANT&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;the_thread&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;resource_count&lt;/span&gt;&lt;span class="w"&gt;         &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;the_thread&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;real_priority&lt;/span&gt;&lt;span class="w"&gt;          &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;priority&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;the_thread&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;Start&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;initial_priority&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;priority&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;libogc from 2023:&lt;/strong&gt;&lt;/p&gt;
&lt;div class="code"&gt;&lt;pre class="code literal-block"&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;thethread&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;budget_algo&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;prio&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="mi"&gt;128&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;LWP_CPU_BUDGET_ALGO_NONE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;LWP_CPU_BUDGET_ALGO_TIMESLICE&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;thethread&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;is_preemptible&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;is_preemtible&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;thethread&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;isr_level&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;isr_level&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;thethread&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;real_prio&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;prio&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;thethread&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;cur_state&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;LWP_STATES_DORMANT&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;thethread&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;cpu_time_budget&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;_lwp_ticks_per_timeslice&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;thethread&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;suspendcnt&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;thethread&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;res_cnt&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;libogc from before 2004:&lt;/strong&gt;&lt;/p&gt;
&lt;div class="code"&gt;&lt;pre class="code literal-block"&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;thethread&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;isr_level&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;isr_level&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;thethread&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;real_prio&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;prio&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;thethread&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;cur_state&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;LWP_STATES_DORMANT&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;thethread&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;suspendcnt&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;thethread&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;res_cnt&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Let's pay attention to the &lt;code&gt;is_preemtible&lt;/code&gt; variable, which is present in
current's libogc and in RTEMS, but not in the old libogc: it was added later.
Now, RTEMS has it since at least 1996, so, if libogc was actually copied from
it, how come this variable was not copied, too, but added at a later time?
Note, the accusation against libogc has been formulated in clear-cut terms:
&lt;em&gt;...steal an open source RTOS and remove all attribution and copyright
information...&lt;/em&gt;; it's clear that this is not what happened. It's much more
likely that libogc's developers did look at RTEMS code (which at that time
&lt;a href="https://github.com/atgreen/RTEMS/blob/2f200c7e642c214accb7cc6bd7f0f1784deec833/LICENSE"&gt;had another
license&lt;/a&gt;
and was known as the &lt;em&gt;Real Time Executive for Missile Systems&lt;/em&gt; or &lt;em&gt;Real Time
Executive for Military Systems&lt;/em&gt;) as a model and source of information, but
since the code was heavily adapted for libogc, they didn't feel they were
creating a “derivative work”. It's a grey area, but even myself, if I took a
project written in C++ and translated it into Rust or C#, for example, I'm
rather sure I wouldn't consider my work to be a derivative of the original; I'm
not a lawyer, so I might be plain wrong here, but I would be in good faith.&lt;/p&gt;
&lt;p&gt;At most, libogc could be accused of plagiarism, but in my humble opinion even
that would be a stretch: since we are not talking of some artistic work, but of
work of science/ingeneering, it's normal to build upon others' work. Yes,
credit should generally be given, but given that we are talking of a US
military-related project, I can see that there could be ethical reasons for not
wanting to do so.&lt;/p&gt;
&lt;h4&gt;Summing up&lt;/h4&gt;
&lt;p&gt;I'm consciously omitting the other old accusations about “stealing” Nintendo's
code, first because they are not new, and secondly because I don't consider
reverse-engineering as stealing. What I found most disturbing about this story
is that it smells hatred from far away, since I have a hard time to understand
why someone felt the need to look into libogc's origin and publicly smearing
the project and ultimately harming the whole of the Wii homebrew community.&lt;/p&gt;</description><guid>http://mardy.it/it/blog/2025/04/no-libogc-did-not-steal-rtems-code.html</guid><pubDate>Mon, 28 Apr 2025 13:56:49 GMT</pubDate></item><item><title>Porting OpenGL games to the Nintendo Wii: chro.mono</title><link>http://mardy.it/it/blog/2025/04/porting-opengl-games-to-the-nintendo-wii-chomono.html</link><dc:creator>Alberto Mardegan</dc:creator><description>&lt;p&gt;More than one year has passed since my last blog post, and I'm a bit ashamed to
confess that today's post is again about porting games to the Nintendo Wii — I
always tell to myself that I'm soon going to move to something else, possibly
less geeky than this (by the way, there &lt;em&gt;is&lt;/em&gt; something else I could write
about, but I'll leave it for another post!), but the problems posed by porting
games to an old console are just way too stimulating, and my brain gets
attracted to them in a way that I cannot resist.&lt;/p&gt;
&lt;p&gt;Anyway, have you ever heard of &lt;a href="https://github.com/devkitPro/opengx"&gt;OpenGX&lt;/a&gt;?
It's a project aiming to write an OpenGL driver for the Nintendo GameCube and
Wii's GX API: while these consoles have good (at the time, at least) 3D
capabilities, they were programmed via an API not even remotely close to
OpenGL, so back in 2013 one developer by the name of David Guillen Fandos
started a project to investigate the possibility of wrapping the GX API in
OpenGL. The project was abandoned after reaching a very basic state, but it's
author was diligent enough to write a &lt;a href="https://github.com/davidgfnet/opengx/blob/master/doc/opengx.pdf"&gt;PDF
file&lt;/a&gt;
describing its design and some implementation details. Having bumped into this
document, I was inspired by it and felt it was a pity that such a project had
died -- especially given the fact that this didn't happen because of some
technical infeasibility. So exactly one year ago, in the spring of 2024, I
picked it up and tried porting the game BillardGL to the Wii; I had to add
quite a few things to OpenGX, and adjusting the lighting pipeline, but after
less than one month &lt;a href="https://github.com/mardy/billardgl/tree/wii-port"&gt;the port of BillardGL was
ready&lt;/a&gt;. In the following
months, I ported several other OpenGL 1.x games, gradually enhancing and
expanding OpenGX, until I got most of OpenGL 1.4 supported.&lt;/p&gt;
&lt;center&gt;
&lt;figure&gt;
  &lt;a href="http://mardy.it/archivos/imagines/blog/billardgl.png"&gt;&lt;img src="http://mardy.it/archivos/imagines/blog/billardgl.png" width="80%"&gt;&lt;/a&gt;
  &lt;figcaption&gt;A 23 year old game was just ported to a 19 year old console.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/center&gt;

&lt;p&gt;Should I have stopped there? Probably. Because indeed the Wii's GPU, despite
allowing a good degree of complexity in setting up its shading engine (called
TEV, Texture Environment), does not support the modern GL shading language, and
trying to have the CPU compile the shaders into something that GX could
understand is a task doomed to fail, in part because this would eat up all the
computing power, but especially because there simply isn't an algorithm that
could translate GLSL into GX commands. Is this the end of the journey then?&lt;/p&gt;
&lt;p&gt;Well, the fact that OpenGL 2.0+ shaders can not be machine-translated into GX
commands does not mean that this task is impossible: we've still got the human
brain to use! The idea is the following: let the deveoper who is porting the
game write the GX code corresponding to the GLSL code by hand. Saying it like
this might feel like this is no better than saying “Just port the whole
rendering backend to GX!” but as a matter of facts, there's a big difference:
with this approach all the rest of the OpenGL code stays untouched, and the way
I have design this &lt;em&gt;shader substitution&lt;/em&gt; to work in OpenGX allows one to keep
the GX code isolated in its own source file, without having to change a single
line of the program, save from adding a line near the beginning (in &lt;code&gt;main()&lt;/code&gt;,
typically) to install the GX hooks. This means that your program will still
call &lt;code&gt;glUniform*()&lt;/code&gt;, &lt;code&gt;glVertexAttribPointer()&lt;/code&gt; and so on to setup the rendering
pipeline, but when the program will get to execute &lt;code&gt;glDrawArrays()&lt;/code&gt; OpenGX will
pass control to the hooks previously installed, which will receive the uniforms
and the attributes, and setup the pipeline using GX commands. It might seem
complicated, but &lt;a href="https://github.com/devkitPro/opengx/commit/f837fe9bd6b363bb6a33f298eae99f15e0cd8bfe"&gt;it really
isn't&lt;/a&gt;
(well, if you can deal with the GX API), and it's even more efficient than the
fixed pipeline of OpenGL 1.x, since here the GX commands are reduced to the
bare minimum required by the shader, whereas in the fixed pipeline we have to
follow predefined steps.&lt;/p&gt;
&lt;center&gt;
&lt;figure&gt;
&lt;iframe src="https://vkvideo.ru/video_ext.php?oid=-230221529&amp;amp;id=456239017&amp;amp;hd=1" width="640" height="360" allow="autoplay; encrypted-media; fullscreen; picture-in-picture; screen-wake-lock;" frameborder="0" allowfullscreen&gt;&lt;/iframe&gt;
  &lt;figcaption&gt;chro.mono running on a Nintendo Wii.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/center&gt;

&lt;p&gt;The first (and for the time being, the last) OpenGL 2.0+ game I've ported to
the Wii is &lt;a href="https://thp.io/2013/chromono/"&gt;chro.mono&lt;/a&gt;, a nice puzzle game from
2013 whose source code has been released in 2021. It uses the FBO feature from
OpenGL 3.0, so I had to implement it in OpenGX as well. I'm quite satisfied
with the result, not only because the game runs at 60 FPS, but because it shows
how rather complex shaders (the game has more than a dozen of them) can be
realized in GX; to tell the truth, in couple of cases I had to implement the
fragment shader by converting its code to C and drawing to a temporary texture,
but luckily these shaders are used only during program startup to draw to an
FBO and don't negatively affect the game performance. You can download it &lt;a href="https://github.com/mardy/chromono/releases"&gt;from
here&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Summing up, if you get really bored and would like to engage in something
unusual (read: useless), porting games to older consoles would definitely keep
you active for some time. Unfortunately there aren't that many OpenGL games
which are open source, so the candidates for porting are not that many (by the
way, feel free to suggest in the comments — well, not for me, but maybe
&lt;em&gt;someone else&lt;/em&gt; will do it).&lt;/p&gt;</description><guid>http://mardy.it/it/blog/2025/04/porting-opengl-games-to-the-nintendo-wii-chomono.html</guid><pubDate>Tue, 22 Apr 2025 15:35:25 GMT</pubDate></item><item><title>libSDL2 and VVVVVV for the Wii</title><link>http://mardy.it/it/blog/2024/02/libsdl2-and-vvvvvv-for-the-wii.html</link><dc:creator>Alberto Mardegan</dc:creator><description>&lt;p&gt;Just a quick update on something that I've been working on in my free time.&lt;/p&gt;
&lt;p&gt;I recently refurbished my old Nintendo Wii, and for some reason I cannot yet
explain (not even to myself) I got into homebrew programming and decided to
port libSDL (the 2.x version -- a 1.x port already existed) to it. The result
of this work is already available via the &lt;a href="https://devkitpro.org/"&gt;devkitPro&lt;/a&gt;
distribution, and although I'm sure there are still many bugs, it's overall
quite usable.&lt;/p&gt;
&lt;p&gt;In order to prove it, I ported the game &lt;a href="https://thelettervsixtim.es/"&gt;VVVVVV&lt;/a&gt;
to the Wii:&lt;/p&gt;
&lt;iframe src="https://vk.com/video_ext.php?oid=7200355&amp;amp;id=456239302&amp;amp;hd=1" width="640" height="360" allow="autoplay; encrypted-media; fullscreen;
picture-in-picture;" frameborder="0" allowfullscreen&gt;&lt;/iframe&gt;

&lt;p&gt;During the process I had to fix quite a few bugs in my libSDL port and in a
couple of other libraries used by VVVVVV, which will hopefully will make it
easier to port more games. There's still an issue that bothers me, where the
screen resolution seems to be not totally supported by my TV (or is it the HDMI
adaptor's fault?), resulting in a few pixels being cut at the top and at the
bottom of the screen. But unless you are a perfectionist, it's a minor issue.&lt;/p&gt;
&lt;p&gt;In case you have a Wii to spare, or wouldn't mind playing on the Dolphin
emulator, &lt;a href="https://github.com/mardy/VVVVVV/releases/tag/v2.4.1_wii1"&gt;here's the link to the VVVVVV
release&lt;/a&gt;. Have fun! :-)&lt;/p&gt;</description><guid>http://mardy.it/it/blog/2024/02/libsdl2-and-vvvvvv-for-the-wii.html</guid><pubDate>Fri, 02 Feb 2024 17:50:44 GMT</pubDate></item><item><title>Deride, a generator of mock objects for unit testing</title><link>http://mardy.it/it/blog/2022/11/deride-a-generator-of-mock-objects-for-unit-testing.html</link><dc:creator>Alberto Mardegan</dc:creator><description>&lt;p&gt;If you have been writing C++ classes for mocking out your C or C++
dependencies, you know how tedious it is. I generally write small classes with
just a handful of methods, so it's generally bearable, but when using
third-party code I'm usually not that lucky. If the dependency is a C library
this becomes especially tricky, both because they might be larger than what you
can handle, and both because the lack of an object-oriented design might not
offer you an easy solution to store the mock object data.&lt;/p&gt;
&lt;p&gt;But fear no more, &lt;a href="https://pypi.org/project/deride/"&gt;Deride&lt;/a&gt; is here!&lt;/p&gt;
&lt;p&gt;I won't spend too many words describing it, since you can read its description
from the link above, where you will also find some example code. More examples,
by the way, can be found in the &lt;code&gt;example/&lt;/code&gt; folder in the &lt;a href="https://gitlab.com/mardy/deride"&gt;code
repository&lt;/a&gt;, where you can see how it can be
used to mock both pure C++ and &lt;code&gt;QObject&lt;/code&gt;-based classes, and C libraries.&lt;/p&gt;
&lt;p&gt;What is most important for me to say now, is that the project is in alpha
state, meaning that I've tried it on a handful of header files only; it's
highly likely that it will not work on many real-life scenarios, and if that
happens I warmly invite you to &lt;a href="https://gitlab.com/mardy/deride/-/issues"&gt;inform me by filing a bug
report&lt;/a&gt; providing the include file
that was not properly processed.&lt;/p&gt;
&lt;p&gt;I leave you with a short example of a unit test, written using Deride. The
class under test is called &lt;code&gt;Stable&lt;/code&gt;, and internally it uses objects of type
&lt;code&gt;Horse&lt;/code&gt;, that we decided to mock. We used Deride to generate the mocked
implementation and a &lt;code&gt;MockHorse&lt;/code&gt; class which can be used to control the mocked
objects. When building the test, we won't link against the original
&lt;code&gt;horse.cpp&lt;/code&gt;, but we'll only use the original &lt;code&gt;horse.h&lt;/code&gt;; the implementation will
be found in &lt;code&gt;mock_horse.cpp&lt;/code&gt;, generated by Deride. And in the corresponding
&lt;code&gt;mock_horse.h&lt;/code&gt; file we'll find the &lt;code&gt;MockHorse&lt;/code&gt; class with all the
&lt;code&gt;on&amp;lt;method&amp;gt;Called()&lt;/code&gt; hooks which we can use to install our callbacks (either to
reimplement the object behaviour, or to just be notified on when its methods
are called).&lt;/p&gt;
&lt;div class="code"&gt;&lt;pre class="code literal-block"&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="cm"&gt;/* This MockHorse is the object created by Deride&lt;/span&gt;
&lt;span class="cm"&gt;     *                   |&lt;/span&gt;
&lt;span class="cm"&gt;     *                   |&lt;/span&gt;
&lt;span class="cm"&gt;     *                  \|/&lt;/span&gt;
&lt;span class="cm"&gt;     *                   V&lt;/span&gt;
&lt;span class="cm"&gt;     */&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;using&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Mock&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Animals&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;MockHorse&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;std&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;list&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;std&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;string&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;horseNames&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="s"&gt;"Tom"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="s"&gt;"Dick"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="s"&gt;"Harry"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="cm"&gt;/* We could use a vector, but let's be explicit */&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;Mock&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;mockTom&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;Mock&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;mockDick&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;Mock&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;mockHarry&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;createdHorses&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="c1"&gt;// onConstructorCalled() is created by Deride and called when the mocked&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="c1"&gt;// Horse object is created&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;Animals&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;MockHorse&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;onConstructorCalled&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="p"&gt;](&lt;/span&gt;&lt;span class="k"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;std&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;string&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;std&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;cout&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"Horse instantiated: "&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;std&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;endl&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;createdHorses&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"Tom"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="n"&gt;mockTom&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Mock&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;latestInstance&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"Dick"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="n"&gt;mockDick&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Mock&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;latestInstance&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"Harry"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="n"&gt;mockHarry&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Mock&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;latestInstance&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="n"&gt;assert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;false&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// should not be reached&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;Stable&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;stable&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;stable&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;createHorses&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;horseNames&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="cm"&gt;/* It's at this point that the contructor callbacks we defined above will&lt;/span&gt;
&lt;span class="cm"&gt;     * have been called. Let's double-check that indeed that's the case.&lt;/span&gt;
&lt;span class="cm"&gt;     */&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;assert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;createdHorses&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;assert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;stable&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;count&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;assert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;mockTom&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;nullptr&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;assert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;mockDick&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;nullptr&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;assert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;mockHarry&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;nullptr&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="cm"&gt;/* Prepare for mocking the jump; these methods are generated by Deride and&lt;/span&gt;
&lt;span class="cm"&gt;     * allow setting the return value for the corresponding jumpHeight() method&lt;/span&gt;
&lt;span class="cm"&gt;     * from the original Horse class. */&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;mockTom&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;setJumpHeightResult&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;1.5&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;mockDick&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;setJumpHeightResult&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;1.7&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;mockHarry&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;setJumpHeightResult&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;1.3&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;std&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;string&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;highestJumper&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;stable&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;findHighestJumper&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;assert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;highestJumper&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"Dick"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;In my closing words I'd like to thank the &lt;a href="https://clang.llvm.org/"&gt;Clang
project&lt;/a&gt;, which Deride is using to parse and interpret
the input files, and &lt;a href="https://pypi.org/project/Jinja2/"&gt;Jinja2&lt;/a&gt;, the templating
engine used to generate the mock code.&lt;/p&gt;</description><guid>http://mardy.it/it/blog/2022/11/deride-a-generator-of-mock-objects-for-unit-testing.html</guid><pubDate>Sat, 05 Nov 2022 06:51:41 GMT</pubDate></item><item><title>Scrum, agility and the human factor</title><link>http://mardy.it/it/blog/2022/10/scrum-agility-and-the-human-factor.html</link><dc:creator>Alberto Mardegan</dc:creator><description>&lt;p&gt;I've been working in Scrum teams for 15 years now, give or take. Different
companies, different approaches, from loosely following the agile principles to
a stricter implementation of the Scrum methodology. The only invariant being
that in practice Scrum is never followed by the book, but every company and
team makes its own adaptations, which makes it hard for everyone to voice
statements and critiques that could be considered universally true. That's why I
will refrain from taking this road, and instead I'll try to point out a few
aspects and behaviours that I've personally noticed during my career, good and
bad ones (but of course, since I'm old and bitter, more bad than good).&lt;/p&gt;
&lt;p&gt;Scrum's focus on communication is, in my opinion, where most of its value lies
on: it's indeed important that other team members know what you are doing, and
that management has an idea of the progress being made. Hence daily standups
and the scrum board, and demos and retrospective at the end of each sprint.&lt;/p&gt;
&lt;p&gt;It makes sense, on paper. And in practice as well, if you find yourself in a
team which is not really a team but a group of individuals with communication
problems. But that should not be the rule, and that's my main criticism of
Scrum: it's &lt;strong&gt;a very good system for managing poorly skilled developers&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;In a highly skilled team, all of the benefits that Scrum is supposed to bring
are already a given: back in 2006-2008, in Nokia, most of our communication was
occurring in IRC and mailing lists. The fact that some of our co-workers were
&lt;a href="https://www.collabora.com/"&gt;remote&lt;/a&gt; indeed acted as a push for this choice.
The same occurred in my early years at Canonical, where (at least in my team)
we had a video conference only once per week: if the communication is already
happening in an open (to the team members) place, there's no need for further
synchronisation points. And if the managers and product owners are also
monitoring these discussions, they know perfectly well how the situation is and
can report it further up.&lt;/p&gt;
&lt;h4&gt;Demos&lt;/h4&gt;
&lt;p&gt;What about demos? Having always worked on middleware and system programming,
where there's very little to &lt;em&gt;show&lt;/em&gt;, I've never been fond of demos. Our team
demos have always been boring and of little significance for most of their
spectators. And while it can take a lot of time to prepare a demo, if we
already communicated what he have done in this sprint, what's the point of
spending more time for the demo? Is it a lack of trust? Or is it a way to make
the team proud of its accomplishments?
In any case, the same could be achieved in a more efficient way: have a
specialized person (maybe from the QA team) prepare and run all the demos. In
this way you'd not only achieve those goals, but you'd also make sure that
there's no cheating (ehmm…) and that bugs are noticed right away.&lt;/p&gt;
&lt;h4&gt;Story estimations&lt;/h4&gt;
&lt;p&gt;Luckily so far I've been involved in only two projects where it was required to
estimate the complexity of the backlog stories. But it was enough to convince
me that there's something fundamentally wrong with it. On one hand, we are
being told that we should estimate &lt;em&gt;complexity&lt;/em&gt;, and not &lt;em&gt;time&lt;/em&gt;, but then the
story points are being used to decide how many stories can fit in a
sprint. It can make sense to estimate the complexity, indeed, because it's an
intrinsic property of the story, which typically does not depend on the actual
person who sits down to work on it; but then what can we use this value for?
And how much time are we willing to spend in order to figure out this nearly
useless magic number? We keep the whole team sitting down together while
throwing estimates, and then question them; and indeed, in order to let other
people express a reasonable estimate we should also explain the story with a
certain level of detail. And while this sounds like a worthwhile thing to do
(explaining the story, I mean), it's probably better to do that in written
form, and ask the team members to read all the stories &lt;em&gt;before&lt;/em&gt; the grooming
session. This is what we have been doing at some point, except that most of the
time there would be at least one colleague who hadn't done his homework, so you
still had to either retell the story during the meeting, or have the complexity
discussion anyway since not all estimates were in agreement.&lt;/p&gt;
&lt;p&gt;Having a clear description on all the stories (where a “clear description”
could even be just a link to a bug report, if that contains enough information)
does not cost a lot of time, as long as the description is written by a single
developer and not during a meeting with the whole team. It brings the benefit
that while writing the story one might realize that there's more to it and this
can lead to the creation of additional stories &lt;em&gt;before&lt;/em&gt; one actually starts the
sprint.&lt;/p&gt;
&lt;p&gt;If, for whatever reason, a point score is absolutely required (just because
upper management wants so), then this should probably happen as the description
is being written, and can be set by the author alone; no need to gather the
whole team for this (since, in any case, the score is inevitably linked to how
the story's creator presents it).&lt;/p&gt;
&lt;h4&gt;Sprint planning&lt;/h4&gt;
&lt;p&gt;It is my firm believe that a team member with no stories assigned should always
be allowed to drag the topmost story from the backlog (who should be kept in
the desired order by the product owner) into the sprint, without needing any
kind of approval from above.&lt;/p&gt;
&lt;p&gt;Unfortunately, I've been working on teams where this was actively discouraged,
if not prohibited. The rationale being that all stories that we bring into the
sprint are a commitment, and that once the &lt;em&gt;TODO&lt;/em&gt; column is empty, everybody who
is free of other work should help their colleagues to complete their stories
which are still in progress.  This sounds good in theory, but in practice it
makes little sense, unless the stories still in progress can be reasonably
split into smaller parts (and most of the times they can't).&lt;/p&gt;
&lt;p&gt;So, a lot of effort is made in order to plan a sprint so that all of its
stories can be completed, and at the end of the sprint one can look at the
burn-down chart and feel proud of that diagonal line having gotten so close to
the horizontal axis.  Except that everybody knows that we are just fooling
ourselves, because that beautiful graph is only the result of us undercommitting
on the sprint planning and then avoiding to bring in more stories into the
sprint once we had the resources to do so.&lt;/p&gt;
&lt;h4&gt;Every developer is worth 1&lt;/h4&gt;
&lt;p&gt;It seems to me that Scrum works under the assumption that all members of a team
are interchangeable, and that it does not matter which developer takes up which
story. It has been like this at least in one of the projects I worked in: as
soon as you'd finish working on your story, it was mandatory to pick the
topmost story from the &lt;em&gt;TODO&lt;/em&gt; column, whatever it might be. Not the second or
the third one: you were allowed to pick the first one only. The reason for this
is that we had some stories that no one wanted to work on, and they'd risk
getting delayed forever.  But this “solution” caused another issue, because if
the topmost item in the &lt;em&gt;TODO&lt;/em&gt; column was an uninspiring story, this made so
that developers would be slower and hesitant in finishing their current
stories; no one would openly talk about that, but it was something you could
easily sense, as people started to find issues in their code, adding more unit
tests than usual, or “inadvertently” picking up the &lt;em&gt;second&lt;/em&gt; topmost story from
the &lt;em&gt;TODO&lt;/em&gt; column.
So, a problem that could have been easily solved by authority (just let the PO
or the lead developer decide who should do what) would become another source of
inefficiency.&lt;/p&gt;
&lt;p&gt;A slightly related issue is the fact that if a team is very inhomogeneous, and
we estimate the stories just by their complexity, we can't really predict
whether our sprint will be a successful one, because it all depends on which
developer works on which story. Believe it or not, I happened to complete
in a handful of hours a story that a teammate had been working on for almost
one month (I assume he was also slacking off, but the point remains): at the
standups he was always reporting “good progress”, until I took the chance of
him having a day off and I stole the story from him.  One might argue that even if
a complexity estimation gives no forecast on when the story will be completed,
it can still be used in cases like this, to detect the underperformers.  This
is totally true, but that's the subject of &lt;a href="http://mardy.it/it/blog/2022/10/performance-review.html"&gt;another blog
post&lt;/a&gt;.&lt;/p&gt;
&lt;h4&gt;Rearranging developers in homogeneous teams&lt;/h4&gt;
&lt;p&gt;Besides, it should not come as a surprise that developers can have very
different skills, up to a factor of hundreds, if we take the extremes, and this
can be due to very different reasons: a low performance could be due to one
being a junior developer, to personality issues, or just to a congenic lack of
an analytic mindset.&lt;/p&gt;
&lt;p&gt;If on one side it makes sense to mix junior and experienced developers in the
same team in order to boost the productivity and skills of the junior
developers, the same does not hold true for good and bad developers: if one
hasn't acquired the needed coding skills after a couple of years of work
experience, it's unlikely that continuing to have this developer work side by
side with more experienced ones will bring much benefit to him. On the opposite
side, it can stress out the more productive developer and demotivate him. Now
that a long time has passed, I can say that this is exactly what happened to me
in a couple of occasions during my career (and the fact that these colleagues
continuously argued and did not recognize their shortcomings did not help).&lt;/p&gt;
&lt;p&gt;You will never get a perfectly homogenous team, since people always have
different experience and skills; anyways, a little of gradation is healthy,
because it stimulates all team members to compete in improving, by seeing each
other as a model to surpass. But this only works if the target is felt as
reachable (otherwise the lowest skilled developer won't have enough motivation)
and worth reaching (otherwise the highest skilled developer will feel he has
nothing to learn from the others).&lt;/p&gt;
&lt;p&gt;That's why, in my opinion, it's worth rearranging teams along skill levels: the
teams with lower skill levels can benefit from being Scrum managed, because
Scrum sets up a frame inside which a low-skilled developer can still be
productive (albeit at a slower pace) thanks to the continuous monitoring and
feedback.  Highly skilled teams — which, incidentally, does not only mean good
coders, but also good teammates with prioritization and communication skills,
and understanding the project goals and nearly capable of managing themselves —
could do very well even without Scrum, and just need a project owner who sets
the goals and defines the high-level stories (generally called “epics”), which
they can then break into smaller tasks by themselves. Adding more Scrum to
such teams might not lead to any improvement.&lt;/p&gt;
&lt;h4&gt;What about managers?&lt;/h4&gt;
&lt;p&gt;I tend to think on a similar vein when it comes to managers (be it project
manager or product owner): my impression is that Scrum is most beneficial when
the team has a poor manager, because it sets some rules that make the figure of
the manager less relevant and reduce it to a role that can be easily performed
by almost anyone. Here I might be totally wrong, of course, since I've never
worked as a manager and there are aspects of their job that are less visible
and that I might be overlooking; but again, I think that a good manager makes
Scrum redundant. Being crystal clear about the team goals, setting up a work
environment that stimulates every team member to give his best, being able to
notice early when things are not going as planned or when someone is
underperforming or not feeling at ease, making sure that the current
development stage is well understood by the rest of the project, etc.: all
these things are also Scrum's goals, so if they are already happening, the
company should cherish this manager as a role model and question whether
Scrum would be really beneficial here.&lt;/p&gt;
&lt;h4&gt;Conclusion&lt;/h4&gt;
&lt;p&gt;In other words, summing up, one company/department should consider introducing
Scrum once a problem has been detected; “as long as the boat goes, let it go”,
as a &lt;a href="https://www.youtube.com/watch?v=pVGGCf_i1y4"&gt;popular Italian song&lt;/a&gt; says.&lt;/p&gt;</description><guid>http://mardy.it/it/blog/2022/10/scrum-agility-and-the-human-factor.html</guid><pubDate>Sat, 29 Oct 2022 07:17:14 GMT</pubDate></item><item><title>Performance reviews</title><link>http://mardy.it/it/blog/2022/10/performance-review.html</link><dc:creator>Alberto Mardegan</dc:creator><description>&lt;p&gt;It happened a few times during my career, that I found myself in a team with a
colleague whose productivity was close to zero. In most of these cases it was
simply a matter of people who hadn't the skills and happened to choose the wrong
career path, and in one case it was actually an excellent developer, but just
slacking off. Regardless of the case, in many of these occasions it looked like
the team manager hadn't noticed the poor performance of the individual in
question, whereas this was rather obvious to the rest of the team.  I'm not
sure why the managers didn't notice the black sheep, but the point is that none
of the other developers did raise the issue either: why would I report a fellow
colleague, who might risk losing his job because of my evil tongue?&lt;/p&gt;
&lt;p&gt;So, Scrum to the rescue? Not quite. As a matter of fact, while it is true that an
underperformer could be easily spotted by seeing how often he fails to complete
his stories in the timeframe suggested by the story points, this information is
generally accessible to the product owner, whereas the line manager might not
attend the Scrum meetings at all (as was the case in a previous project of
mine, where the line manager was completely detached from the project); and
even if the line manager had this information, it's not a given that he'd make
use of it — as a matter of fact, I cannot say with certainty that the line
managers did not notice those underperforming colleagues of mine; maybe they
noticed, but failed to intervene for some reason?&lt;/p&gt;
&lt;p&gt;It sounds like this might be
&lt;a href="https://en.wikipedia.org/wiki/360-degree_feedback"&gt;360&lt;/a&gt; material.
Unfortunately, in my experience the 360 reviews are a waste of time for the
most part, but that might be because they had been badly implemented in the
companies where I worked in.  In these reviews I get to rate my colleagues
using a set of predefined statements which generally look positive, such as
“Often delivers more than expected” or “Always meets the expectations”, but
each of which imply a very different rating.  I can see that the reason why the
system uses this kind of sentences is because no one wants to explicitly assign
a bad rating to a colleague, so all the possible answers have a “positive”
feeling.  The problem with this is that the shades of meaning in these
sentences is not obvious at all, so one risks ending up picking sentences
almost at random.&lt;/p&gt;
&lt;p&gt;A system that would allow a company to get a honest feedback, without requiring
employees to say bad things about their colleagues, could be based on the idea
that your colleagues have usually a good sense of how well you are doing.  To
me, it would make more sense if the performance review consisted of a question
like this:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Please make a list of those colleagues that in your opinion are more valuable
to your team or to the company in general.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Then the company should sum up these lists and have a look at who is &lt;em&gt;not&lt;/em&gt;
there, or whose name appears way too few times in relation to the number of
people who have worked with him or her. Then this information would not only be
available to the direct line manager, but also to upper line managers, who
might be willing to judge the situation with more objectivity and be able to
decide to move the person to another team.&lt;/p&gt;
&lt;p&gt;The presentation of this question could indeed be very different from what I've
suggested here, for example it could be something like “Make a list of
colleagues you'd be most happy to work (or continue working) with”, or it could
include some personal feedback: in that way, if the only good thing that people
have to say about a developer is “He's a very nice guy”, well, you could
imagine that we are not dealing with a strong developer after all.&lt;/p&gt;
&lt;p&gt;On the opposite side of the spectrum, the people whose names appear more often
in the &lt;em&gt;star colleagues&lt;/em&gt; lists are probably the employees that the company
should cherish and try hard not to lose. Salary increases, bonuses and all
other gratifications that can help in retaining them should be primarily
connected to the colleagues' direct feedback, rather than to semi-obscure
metrics which might not capture their real value.&lt;/p&gt;</description><guid>http://mardy.it/it/blog/2022/10/performance-review.html</guid><pubDate>Thu, 27 Oct 2022 05:47:14 GMT</pubDate></item><item><title>MiTubo 1.4 adds feed folders</title><link>http://mardy.it/it/blog/2022/10/mitubo-14-adds-feed-folders.html</link><dc:creator>Alberto Mardegan</dc:creator><description>&lt;p&gt;Exactly one month has passed since the previous release, just the right time
needed to complete the feafure I've been working on since several weeks and to
fix a few bugfixes introduced with the previous release. So it's time a new
release of &lt;a href="https://gitlab.com/mardy/mitubo"&gt;MiTubo&lt;/a&gt;:&lt;/p&gt;
&lt;center&gt;
&lt;video id="video" controls preload="metadata" width="100%"&gt;
   &lt;source src="http://mardy.it/archivos/videos/mitubo-1.4.webm" type="video/webm"&gt;
&lt;/source&gt;&lt;/video&gt;
&lt;/center&gt;

&lt;p&gt;I realized that I'm not that good at making release videos, but the point of
the video above is to show that you can organize your feeds into folders. When
clicking on a folder, a page opens with the folder's contents; but you can also
directly click on a feed, as long as its preview is visible in the folder's
delegate, and then the feeds open directly. This means that if you organize the
feeds inside your folders so that the favourite ones are at the top, they'll
also be visible in the folder preview and you'll be able to jump to them in
just one click.&lt;/p&gt;
&lt;p&gt;Maybe I'm not that good with textual explanations either, so why don't you
check it out for yourself? ☺  Get it at
&lt;a href="http://www.mardy.it/mitubo"&gt;mardy.it/mitubo&lt;/a&gt; (builds for Linux, Ubuntu Touch,
Windows and macOS are available)!&lt;/p&gt;</description><guid>http://mardy.it/it/blog/2022/10/mitubo-14-adds-feed-folders.html</guid><pubDate>Mon, 10 Oct 2022 14:40:09 GMT</pubDate></item><item><title>MiTubo 1.3: sorting of QML ListView via Drag&amp;Drop</title><link>http://mardy.it/it/blog/2022/09/mitubo-13-sorting-of-qml-listview-via-dragdrop.html</link><dc:creator>Alberto Mardegan</dc:creator><description>&lt;p&gt;One feature that I've been asked to add to
&lt;a href="https://gitlab.com/mardy/mitubo"&gt;MiTubo&lt;/a&gt;, and that indeed becomes more and
more important as the number of subscriptions increases, is the ability to
group subscriptions into folders. I've spent a good amount of time implementing
the needed support in the C++ backend, which is now able to handle nested
folders too, but given that building the UI parts was not a quick task and
seeing how much time has passed since the last release, I thought of releasing
a partial implementation of the whole feature, consisting only of the ability
to manually sort the subscriptions via drag&amp;amp;drop (that, is no folder support).
It turns out this is already not a trivial work!&lt;/p&gt;
&lt;p&gt;I found a &lt;a href="https://agateau.com/2016/reordering-a-listview-via-dragndrop-3/"&gt;nice tutorial on ListView DnD
sorting&lt;/a&gt; by
the great Aurélien Gâteau which I found very inspiring, and while I didn't
actually reuse the same code (mostly because I was already halfway through with
my implementation, which I started before finding his tutorial), it was helpful
to have it as a reference. I added a few animations to make it look more
pleasant, and I'm rather satisfied with the result:&lt;/p&gt;
&lt;center&gt;
&lt;video id="video" controls preload="metadata" width="100%"&gt;
   &lt;source src="http://mardy.it/archivos/videos/mitubo-1.3.webm" type="video/webm"&gt;
&lt;/source&gt;&lt;/video&gt;
&lt;/center&gt;

&lt;p&gt;I'm not showing you the code yet (though, indeed, you can find it in the
&lt;code&gt;DraggableListView&lt;/code&gt; and &lt;code&gt;DraggableDelegate&lt;/code&gt; items &lt;a href="https://gitlab.com/mardy/mitubo/-/tree/master/src/desktop/qml"&gt;in the source
repository&lt;/a&gt;)
because it's not yet in a shape where it's generally reusable in other
projects, but if I happen to need the same feature elsewhere I'll eventually
try to turn it into a couple fully reusable components.&lt;/p&gt;
&lt;p&gt;Anyway, here's what's new in this latest MiTubo release:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Subscriptions can be sorted by means of Drag&amp;amp;drop&lt;/li&gt;
&lt;li&gt;For systems with python 3.5 or older (such as Ubuntu Touch), use the daily
  builds of &lt;a href="https://youtube-dl.org/"&gt;youtube-dl&lt;/a&gt; instead of the official
  releases&lt;/li&gt;
&lt;li&gt;Implement &lt;strong&gt;PeerTube search&lt;/strong&gt; (this should reserve a post of its own!)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;You can get it &lt;a href="http://www.mardy.it/mitubo"&gt;at the usual place&lt;/a&gt;. This time
there are only Linux and Windows builds as I'm a bit lazy to make a macOS
version, but should you need it, don't hesitate to ask!&lt;/p&gt;</description><guid>http://mardy.it/it/blog/2022/09/mitubo-13-sorting-of-qml-listview-via-dragdrop.html</guid><pubDate>Sat, 10 Sep 2022 10:38:51 GMT</pubDate></item><item><title>MiTubo comes to macOS</title><link>http://mardy.it/it/blog/2022/06/mitubo-comes-to-macos.html</link><dc:creator>Alberto Mardegan</dc:creator><description>&lt;p&gt;I just released &lt;a href="http://mardy.it/mitubo/#downloads"&gt;MiTubo 1.2&lt;/a&gt;. New in this version:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;As suggested by &lt;code&gt;alphas12&lt;/code&gt; in the comments, I added the author name in the
  YouTube search results.&lt;/li&gt;
&lt;li&gt;In the same results list, there's now a clickable link to the channel, which
  makes it easier to subscribe to it.&lt;/li&gt;
&lt;li&gt;Improve layout of some pages on narrow displays (though there's still much to
  be done!).&lt;/li&gt;
&lt;li&gt;Skip invoking youtube-dl if the video information is already encoded in the
  page &lt;code&gt;HEAD&lt;/code&gt; meta properties.&lt;/li&gt;
&lt;li&gt;Remember the preferred playback resolution; this can be helpful on low
  bandwith connections.&lt;/li&gt;
&lt;li&gt;First macOS release!&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;While bringing in the macOS version, I updated the &lt;a href="https://gitlab.com/qt-goodies/qscreensaver"&gt;QScreenSaver
library&lt;/a&gt; to support inhibiting the
screensaver on macOS too.&lt;/p&gt;
&lt;p&gt;I also tested the AppImage on openSUSE, and it seems to work fine there too.
So, fewer and fewer people have valid excuses not to try out MiTubo!&lt;/p&gt;</description><guid>http://mardy.it/it/blog/2022/06/mitubo-comes-to-macos.html</guid><pubDate>Wed, 22 Jun 2022 19:44:47 GMT</pubDate></item></channel></rss>