<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom"><title>nfraprado</title><link href="https://nfraprado.net/" rel="alternate"/><link href="https://nfraprado.net/feeds/all.atom.xml" rel="self"/><id>https://nfraprado.net/</id><updated>2026-04-07T00:00:00-03:00</updated><entry><title>Favorite Game: Hyperbeat</title><link href="https://nfraprado.net/post/favorite-game-hyperbeat.html" rel="alternate"/><published>2026-04-07T00:00:00-03:00</published><updated>2026-04-07T00:00:00-03:00</updated><author><name>Nícolas F. R. A. Prado</name></author><id>tag:nfraprado.net,2026-04-07:/post/favorite-game-hyperbeat.html</id><summary type="html">&lt;p&gt;Every once in a while you play a game that is unlike any other, that transcends genres, that you can feel the author speaking through it to you, and that you can tell will stick around in your mind in some way or another probably forever. &lt;a class="reference external" href="https://store.steampowered.com/app/2263360/HYPERBEAT/"&gt;Hyperbeat&lt;/a&gt; is one of …&lt;/p&gt;</summary><content type="html">&lt;p&gt;Every once in a while you play a game that is unlike any other, that transcends genres, that you can feel the author speaking through it to you, and that you can tell will stick around in your mind in some way or another probably forever. &lt;a class="reference external" href="https://store.steampowered.com/app/2263360/HYPERBEAT/"&gt;Hyperbeat&lt;/a&gt; is one of those games.&lt;/p&gt;
&lt;p&gt;I love how the way to interact with anything in the hub is to just wait while a musical rest fills in, I love the 1 FPS walking animation, I love how philosophical the entire narrative and dialogues are, and how the different songs are used to emphasize feelings in the dialogue. And I LOVE the soundtrack.&lt;/p&gt;
&lt;p&gt;The gameplay is hard, yes, and I missed many beats even in the tutorial. But you can clearly feel yourself improving as you play, and it's very rewarding.&lt;/p&gt;
&lt;p&gt;This is how my character looked by the end of the game:&lt;/p&gt;
&lt;img alt="{image}/character.png" src="/images/hyperbeat/character.png" /&gt;
&lt;p&gt;Thank you to &lt;a class="reference external" href="https://www.youtube.com/watch?v=Og6_t5eiz_s"&gt;Noclip2's Computer Worlds video&lt;/a&gt; for featuring this game, otherwise I might have never experienced it.&lt;/p&gt;
&lt;p&gt;Now if you'll excuse I'm going out for a walk, because I feel excited and I can't sit still while listening to the &lt;a class="reference external" href="https://lostincult.bandcamp.com/album/hyperbeat-original-game-soundtrack"&gt;Hyperbeat soundtrack&lt;/a&gt;.&lt;/p&gt;
</content><category term="2026"/><category term="favorite-game"/></entry><entry><title>2025 retrospective with shirts</title><link href="https://nfraprado.net/post/2025-retrospective-with-shirts.html" rel="alternate"/><published>2025-12-29T00:00:00-03:00</published><updated>2025-12-29T00:00:00-03:00</updated><author><name>Nícolas F. R. A. Prado</name></author><id>tag:nfraprado.net,2025-12-29:/post/2025-retrospective-with-shirts.html</id><summary type="html">&lt;p&gt;This year I bought more shirts than ever. Mostly because I decided to go to as
many concerts as I possibly could.&lt;/p&gt;
&lt;p&gt;With the year coming to a close, I thought this would be a good moment to look
back at the shirts I collected throughout the year and see …&lt;/p&gt;</summary><content type="html">&lt;p&gt;This year I bought more shirts than ever. Mostly because I decided to go to as
many concerts as I possibly could.&lt;/p&gt;
&lt;p&gt;With the year coming to a close, I thought this would be a good moment to look
back at the shirts I collected throughout the year and see the story that they
tell. So here it is, my 2025 retrospective through shirts:&lt;/p&gt;
&lt;img alt="{image}/shirts-collage.jpg" src="/images/2025-shirts/shirts-collage.jpg" /&gt;
&lt;p&gt;From top left to bottom right:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;Row 1, Column 1: March. Went to a Three Days Grace and Disturbed concert.
Three Days Grace was my favorite band as a teenager, and on the day of the
concert I found out that Adam Gontier, the lead singer, who had left the band
several years before, had just joined back. That show was a dream come true
for my inner teenager.&lt;/li&gt;
&lt;li&gt;1-2: March. I saw someone wearing this shirt in a picture of a local
volunteering group I was part of and immediately asked where I could buy one.
In fact I bought two extra to gift them to friends. It has been my most
popular shirt by far, with many compliments received on different occasions
and someone once even asked me if they could take a picture of it. If you also
love it, check it out here:
&lt;a class="reference external" href="https://mtpfriends.bigcartel.com/product/what-s-more-punk-adult-t-shirt"&gt;https://mtpfriends.bigcartel.com/product/what-s-more-punk-adult-t-shirt&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;1-3: April. Went to a social event in the neighborhood, and saw a local, and
pretty good, band called Lolux.&lt;/li&gt;
&lt;li&gt;1-4: May. Went to a Bloc Party concert at the Forest Hills Stadium. I came out
of it feeling underwhelmed by the sound quality but it was fun nevertheless.
Plus it made me discover Forest Hills, which I found a very quaint
neighborhood that is hard to believe exists in New York City.&lt;/li&gt;
&lt;li&gt;2-1: June. &lt;a class="reference external" href="https://www.wonderville.nyc/"&gt;Wonderville&lt;/a&gt; is simply put my favorite place in NYC. As they put
it, they're &amp;quot;Brooklyn’s home for independently made arcade games&amp;quot;. The place
looks very much cyberpunk-y, the games are all free to play, and they
frequently have unique events, from livecoding to game jams to beyblade
tournaments. This shirt was made to commemorate their 6 year anninversary,
which I pre-ordered but ended up not making it to the party. I grabbed the
shirt a few weeks later when I went there to show the place to a friend that
was visiting NYC, something that has become a tradition for me (this is the
third visiting friend I bring there).&lt;/li&gt;
&lt;li&gt;2-2: June. Went to a Godspeed You! Black Emperor concert in Norwalk, CT. The
1-hour train ride to a small town definitely added to the vibe. The concert
was really unique, featuring a stage that was almost completely dark with
hand-crafted film projections showing behind, and a sound that would slowly
build up from a lonely silence to an overwhelming array of layered distorted
guitars and violins. Listen to &lt;a class="reference external" href="https://www.youtube.com/watch?v=cQcE4_7-X78"&gt;Sleep&lt;/a&gt; to get a glimpse of it, but
seeing it live was something else.&lt;/li&gt;
&lt;li&gt;2-3: July. Went to a live Welcome to Night Vale show in Brooklyn. I used to
listen to this podcast while in college, so it was quite nostalgic to hear it
again, and seeing it live, with crowd interactions made it even more fun.&lt;/li&gt;
&lt;li&gt;2-4: July. Went to see Creed at the Jones Beach Theater. Beautiful outdoor
venue facing the sea, and it was awesome to hear them live. This was the first
concert that I went with my mom and the fact that we took a train there and
stayed at a hotel until the next day made it feel like a short vacation trip.&lt;/li&gt;
&lt;li&gt;3-1: July. I love post-rock, and small concerts, so even though I had only
listened to a single album from We Lost the Sea many years before, I couldn't
pass on the opportunity to see them for just 25$. It was a blast, those cheap
small concerts are the best.&lt;/li&gt;
&lt;li&gt;3-2: August. In contrast, shortly after I payed 171$, the most expensive
tickets of my life, to see Linkin Park at the Prudential Center in Newark. The
view was good, but my seat was so far high that I felt detached from the
concert. Also despite getting this shirt in the same size I always get them,
this one is absolutely huge.&lt;/li&gt;
&lt;li&gt;3-3: August. Went again to the Forest Hills Stadium, this time to see The
Black Keys with a friend. It started raining heavily (ironically given the
shirt text) shortly after they started playing, but luckily it stopped in time
for the show to resume, despite being a bit shorter.&lt;/li&gt;
&lt;li&gt;3-4: September. Went to see Breaking Benjamin and Three Days Grace (again!).
Despite being my favorite band as a teen, seeing TDG this second time was much
worse, and gave me the feeling it's not worth it to see a band twice. They
played pretty much all the same songs, and this time they had a big screen
behind showing clumsy visuals that were clearly AI-generated. Thankfully
Breaking Benjamin was fantastic and made it worth it.&lt;/li&gt;
&lt;li&gt;4-1: October. The night before heading out on a trip, I saw Anamanaguchi at
the Brooklyn Steel. I only knew two of their songs, of which they only played
one, but I really liked their new album that I first heard at their concert.
They also played Hopes and Dreams from Undertale, to celebrate the creator's
birthday, and that made this a unique and unforgettable experience.&lt;/li&gt;
&lt;li&gt;4-2: October. I spent the largest part of October on a road trip throughout
California with a friend as I mentioned in the &lt;a class="reference external" href="/post/song-of-the-month-christopher-cross-sailing.html"&gt;"Song of the Month: Christopher Cross - Sailing" post&lt;/a&gt;.
There were many natural wonders, but my favorite were the sequoias at Sequoia
National Park. That's why I had to stop at the gift shop at the park and get a
shirt (and pins!) as mementos of this wonderful place.&lt;/li&gt;
&lt;li&gt;4-3: November. And to finish off the year, saw Of Monsters and Men at the
Brooklyn Paramount with my girlfriend. The pit was pretty busy so it was hard
to see at times, but the venue was really gorgeous and we had a good time.
This shirt is a long sleeve, the first one I got in over ten years, and it's
so pretty. I think it's my favorite shirt of the year.&lt;/li&gt;
&lt;/ul&gt;
</content><category term="2025"/><category term="clothing"/></entry><entry><title>Song of the Month: Andy Williams - Do You Hear What I Hear?</title><link href="https://nfraprado.net/post/song-of-the-month-andy-williams-do-you-hear-what-i-hear.html" rel="alternate"/><published>2025-12-01T00:00:00-03:00</published><updated>2025-12-01T00:00:00-03:00</updated><author><name>Nícolas F. R. A. Prado</name></author><id>tag:nfraprado.net,2025-12-01:/post/song-of-the-month-andy-williams-do-you-hear-what-i-hear.html</id><summary type="html">&lt;p&gt;Slightly late for November, but better late than never!&lt;/p&gt;
&lt;p&gt;As November rolled by, and the Christmas mood took over, I put on an &lt;a class="reference external" href="https://www.youtube.com/watch?v=g8UmqvOqB1A&amp;amp;list=PLBN3i8jrg4Wze3v_KZRPFmnQrTw5jia61&amp;amp;index=1"&gt;old school Christmas songs playlist on YouTube&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Many of the songs I was hearing for the first time. A few of my favorites were &lt;a class="reference external" href="https://www.youtube.com/watch?v=lvVfIi0mQx4"&gt;Carol of the …&lt;/a&gt;&lt;/p&gt;</summary><content type="html">&lt;p&gt;Slightly late for November, but better late than never!&lt;/p&gt;
&lt;p&gt;As November rolled by, and the Christmas mood took over, I put on an &lt;a class="reference external" href="https://www.youtube.com/watch?v=g8UmqvOqB1A&amp;amp;list=PLBN3i8jrg4Wze3v_KZRPFmnQrTw5jia61&amp;amp;index=1"&gt;old school Christmas songs playlist on YouTube&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Many of the songs I was hearing for the first time. A few of my favorites were &lt;a class="reference external" href="https://www.youtube.com/watch?v=lvVfIi0mQx4"&gt;Carol of the Bells&lt;/a&gt;, &lt;a class="reference external" href="https://www.youtube.com/watch?v=oQ_2K6Jk8vs"&gt;The First Noel&lt;/a&gt; and &lt;a class="reference external" href="https://www.youtube.com/watch?v=4EvZOXEoJ84"&gt;Darlene Love - Christmas (Baby Please Come Home)&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;But my favorite of all has to be &amp;quot;Do You Hear What I Hear?&amp;quot;, particularly the version sung by Andy Williams, so that's my pick for the month:&lt;/p&gt;
&lt;p&gt;&lt;a class="reference external" href="https://www.youtube.com/watch?v=ksIZijfEuoY"&gt;Andy Williams - Do You Hear What I Hear?&lt;/a&gt;&lt;/p&gt;
</content><category term="2025"/><category term="song-of-the-month"/></entry><entry><title>Song of the Month: Christopher Cross - Sailing</title><link href="https://nfraprado.net/post/song-of-the-month-christopher-cross-sailing.html" rel="alternate"/><published>2025-10-28T00:00:00-03:00</published><updated>2025-10-28T00:00:00-03:00</updated><author><name>Nícolas F. R. A. Prado</name></author><id>tag:nfraprado.net,2025-10-28:/post/song-of-the-month-christopher-cross-sailing.html</id><summary type="html">&lt;p&gt;This month I went on a two week long road trip through California with a friend.
We sailed on Lake Tahoe, walked over sand dunes in Death Valley, and hiked
between the mountains in Yosemite Valley. We saw the oldest trees on Earth at
the Ancient Bristlecone Pine Forest, and …&lt;/p&gt;</summary><content type="html">&lt;p&gt;This month I went on a two week long road trip through California with a friend.
We sailed on Lake Tahoe, walked over sand dunes in Death Valley, and hiked
between the mountains in Yosemite Valley. We saw the oldest trees on Earth at
the Ancient Bristlecone Pine Forest, and also the largest trees, at Sequoia
National Park, through a mysterious fog.&lt;/p&gt;
&lt;p&gt;We listened to many songs throughout the trip, but there was one that popped up
and seemingly immediately won my friend over, as he kept putting it back on time
and again. It is indeed a beautiful song, and will forever carry the memory of
this amazing trip. It is my pick for this month:&lt;/p&gt;
&lt;p&gt;&lt;a class="reference external" href="https://www.youtube.com/watch?v=MEO6gYCFbr0"&gt;Christopher Cross - Sailing&lt;/a&gt;&lt;/p&gt;
&lt;img alt="{image}/general-sherman.jpg" src="/images/song-202510/general-sherman.jpg" /&gt;
</content><category term="2025"/><category term="song-of-the-month"/></entry><entry><title>Song of the Month: Moin - Lift You</title><link href="https://nfraprado.net/post/song-of-the-month-moin-lift-you.html" rel="alternate"/><published>2025-09-30T00:00:00-03:00</published><updated>2025-09-30T00:00:00-03:00</updated><author><name>Nícolas F. R. A. Prado</name></author><id>tag:nfraprado.net,2025-09-30:/post/song-of-the-month-moin-lift-you.html</id><summary type="html">&lt;p&gt;My pick for song of the month this time around is a track that played at &lt;a class="reference external" href="https://www.spectacletheater.com/"&gt;Spectacle Theater&lt;/a&gt; before the movie started (the movie was &lt;a class="reference external" href="https://www.spectacletheater.com/i-know-youre-out-there/#miragemen"&gt;Mirage Men (2013)&lt;/a&gt;, by the way). The lyrics are very poetic, and the instrumentation has this eerie and underground feel to it. It's a great …&lt;/p&gt;</summary><content type="html">&lt;p&gt;My pick for song of the month this time around is a track that played at &lt;a class="reference external" href="https://www.spectacletheater.com/"&gt;Spectacle Theater&lt;/a&gt; before the movie started (the movie was &lt;a class="reference external" href="https://www.spectacletheater.com/i-know-youre-out-there/#miragemen"&gt;Mirage Men (2013)&lt;/a&gt;, by the way). The lyrics are very poetic, and the instrumentation has this eerie and underground feel to it. It's a great song on its own, but it was elevated by being played at one of my favorite places in NYC and perfectly matching the vibe.&lt;/p&gt;
&lt;p&gt;Check it out:&lt;/p&gt;
&lt;p&gt;&lt;a class="reference external" href="https://m-o-i-n.bandcamp.com/track/lift-you-feat-sophia-al-maria"&gt;Moin - Lift You&lt;/a&gt;&lt;/p&gt;
</content><category term="2025"/><category term="song-of-the-month"/></entry><entry><title>Pin collection update (128 pins in three images and a table)</title><link href="https://nfraprado.net/post/pin-collection-update-128-pins-in-three-images-and-a-table.html" rel="alternate"/><published>2025-09-04T00:00:00-03:00</published><updated>2025-09-04T00:00:00-03:00</updated><author><name>Nícolas F. R. A. Prado</name></author><id>tag:nfraprado.net,2025-09-04:/post/pin-collection-update-128-pins-in-three-images-and-a-table.html</id><summary type="html">&lt;p&gt;It's been almost two years since I posted the &lt;a class="reference external" href="/post/collecting-pins.html"&gt;"Collecting pins" post&lt;/a&gt;! It's time I
shared an update.&lt;/p&gt;
&lt;p&gt;Somewhere along the way I decided to get more organized and started a
spreadsheet to track all the details about my pin collection (has this crossed
the line to the unhealthy hobby …&lt;/p&gt;</summary><content type="html">&lt;p&gt;It's been almost two years since I posted the &lt;a class="reference external" href="/post/collecting-pins.html"&gt;"Collecting pins" post&lt;/a&gt;! It's time I
shared an update.&lt;/p&gt;
&lt;p&gt;Somewhere along the way I decided to get more organized and started a
spreadsheet to track all the details about my pin collection (has this crossed
the line to the unhealthy hobby territory yet?). The main thing I wanted to have
was a clear history of the collection: when, where and how was each pin added to
the collection. And after spending a lot of time filling in the blanks, today I
can finally share it!&lt;/p&gt;
&lt;p&gt;So here it is, my current pin collection, with 128 pins and filling up two and a
half display cases, summarized in three images and a table:&lt;/p&gt;
&lt;img alt="{image}/display1.jpg" src="/images/pins-spreadsheet/display1.jpg" /&gt;
&lt;img alt="{image}/display2.jpg" src="/images/pins-spreadsheet/display2.jpg" /&gt;
&lt;img alt="{image}/display3.jpg" src="/images/pins-spreadsheet/display3.jpg" /&gt;
&lt;table border="1" class="docutils"&gt;
&lt;colgroup&gt;
&lt;col width="20%" /&gt;
&lt;col width="20%" /&gt;
&lt;col width="20%" /&gt;
&lt;col width="20%" /&gt;
&lt;col width="20%" /&gt;
&lt;/colgroup&gt;
&lt;thead valign="bottom"&gt;
&lt;tr&gt;&lt;th class="head"&gt;No.&lt;/th&gt;
&lt;th class="head"&gt;Name&lt;/th&gt;
&lt;th class="head"&gt;When&lt;/th&gt;
&lt;th class="head"&gt;Where&lt;/th&gt;
&lt;th class="head"&gt;How&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody valign="top"&gt;
&lt;tr&gt;&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;&lt;a class="reference external" href="https://www.etsy.com/listing/587004849/poring-poporing-slime-monter-fanart-hard"&gt;Poring — Ragnarok Online&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;2022-12-01&lt;/td&gt;
&lt;td&gt;Online&lt;/td&gt;
&lt;td&gt;Purchase&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;&lt;a class="reference external" href="https://www.etsy.com/listing/587004849/poring-poporing-slime-monter-fanart-hard"&gt;Poporing — Ragnarok Online&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;2022-12-01&lt;/td&gt;
&lt;td&gt;Online&lt;/td&gt;
&lt;td&gt;Purchase&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;&lt;a class="reference external" href="https://www.etsy.com/listing/742804941/deviruchi-gg-ragnarok-online-enamel-pin"&gt;/Gg Emote — Ragnarok Online&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;2022-12-16&lt;/td&gt;
&lt;td&gt;Online&lt;/td&gt;
&lt;td&gt;Purchase&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;&lt;a class="reference external" href="https://www.etsy.com/listing/742804941/deviruchi-gg-ragnarok-online-enamel-pin"&gt;Deviruchi — Ragnarok Online&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;2022-12-16&lt;/td&gt;
&lt;td&gt;Online&lt;/td&gt;
&lt;td&gt;Purchase&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;&lt;a class="reference external" href="https://www.etsy.com/listing/842366471/?variation0=1473891219"&gt;Fairy Bottle — Zelda&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;2022-12-16&lt;/td&gt;
&lt;td&gt;Online&lt;/td&gt;
&lt;td&gt;Purchase&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;6&lt;/td&gt;
&lt;td&gt;&lt;a class="reference external" href="https://www.etsy.com/listing/729815460/eeveelution-hard-enamel-pins-complete?variation0=1222773687"&gt;Eevee — Pokémon&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;2022-12-21&lt;/td&gt;
&lt;td&gt;Online&lt;/td&gt;
&lt;td&gt;Purchase&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;7&lt;/td&gt;
&lt;td&gt;L — Death Note&lt;/td&gt;
&lt;td&gt;2023-02-08&lt;/td&gt;
&lt;td&gt;Geek store in Antwerp, Belgium&lt;/td&gt;
&lt;td&gt;Purchase&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;8&lt;/td&gt;
&lt;td&gt;Phantom Of The Opera&lt;/td&gt;
&lt;td&gt;2023-02-13&lt;/td&gt;
&lt;td&gt;Phantom of the Opera show at Majestic Theatre in NYC&lt;/td&gt;
&lt;td&gt;Purchase&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;9&lt;/td&gt;
&lt;td&gt;&lt;a class="reference external" href="https://store.moma.org/products/artist-enamel-pin-van-gogh"&gt;Van Gogh's The Starry Night&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;2023-03-11&lt;/td&gt;
&lt;td&gt;MoMA in NYC&lt;/td&gt;
&lt;td&gt;Purchase&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;10&lt;/td&gt;
&lt;td&gt;&lt;a class="reference external" href="https://www.fangamer.com/collections/katamari-damacy/products/king-of-all-cosmos-pin-spinning"&gt;King — Katamari Damacy&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;2023-03-23&lt;/td&gt;
&lt;td&gt;Fangamer stand at PAX East 23&lt;/td&gt;
&lt;td&gt;Purchase&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;11&lt;/td&gt;
&lt;td&gt;&lt;a class="reference external" href="https://www.fangamer.com/collections/katamari-damacy/products/katamari-damacy-the-prince-katamari-spinning-pin"&gt;Prince &amp;amp; Katamari — Katamari Damacy&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;2023-03-23&lt;/td&gt;
&lt;td&gt;Fangamer stand at PAX East 23&lt;/td&gt;
&lt;td&gt;Purchase&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;12&lt;/td&gt;
&lt;td&gt;&lt;a class="reference external" href="https://www.fangamer.com/collections/undertale/products/undertale-annoying-dog-lapel-pin"&gt;Annoying Dog — Undertale&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;2023-03-23&lt;/td&gt;
&lt;td&gt;Fangamer stand at PAX East 23&lt;/td&gt;
&lt;td&gt;Purchase&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;13&lt;/td&gt;
&lt;td&gt;&lt;a class="reference external" href="https://www.fangamer.com/collections/undertale/products/undertale-character-pins-set-1"&gt;Flowey — Undertale&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;2023-03-23&lt;/td&gt;
&lt;td&gt;Fangamer stand at PAX East 23&lt;/td&gt;
&lt;td&gt;Purchase&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;14&lt;/td&gt;
&lt;td&gt;&lt;a class="reference external" href="https://www.fangamer.com/collections/undertale/products/undertale-character-pins-set-1"&gt;Sans — Undertale&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;2023-03-23&lt;/td&gt;
&lt;td&gt;Fangamer stand at PAX East 23&lt;/td&gt;
&lt;td&gt;Purchase&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;15&lt;/td&gt;
&lt;td&gt;&lt;a class="reference external" href="https://www.fangamer.com/collections/undertale/products/undertale-character-pins-set-1"&gt;Frisk — Undertale&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;2023-03-23&lt;/td&gt;
&lt;td&gt;Fangamer stand at PAX East 23&lt;/td&gt;
&lt;td&gt;Purchase&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;16&lt;/td&gt;
&lt;td&gt;&lt;a class="reference external" href="https://www.fangamer.com/collections/undertale/products/undertale-character-pins-set-2"&gt;Papyrus — Undertale&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;2023-03-23&lt;/td&gt;
&lt;td&gt;Fangamer stand at PAX East 23&lt;/td&gt;
&lt;td&gt;Purchase&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;17&lt;/td&gt;
&lt;td&gt;&lt;a class="reference external" href="https://www.fangamer.com/collections/undertale/products/undertale-character-pins-set-1"&gt;Toriel — Undertale&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;2023-03-23&lt;/td&gt;
&lt;td&gt;Fangamer stand at PAX East 23&lt;/td&gt;
&lt;td&gt;Purchase&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;18&lt;/td&gt;
&lt;td&gt;&lt;a class="reference external" href="https://www.fangamer.com/collections/undertale/products/undertale-character-pins-set-2"&gt;Undyne — Undertale&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;2023-03-23&lt;/td&gt;
&lt;td&gt;Fangamer stand at PAX East 23&lt;/td&gt;
&lt;td&gt;Purchase&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;19&lt;/td&gt;
&lt;td&gt;&lt;a class="reference external" href="https://www.fangamer.com/collections/undertale/products/undertale-mercy-or-fight-enamel-pin-set"&gt;Mercy Button — Undertale&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;2023-03-23&lt;/td&gt;
&lt;td&gt;Fangamer stand at PAX East 23&lt;/td&gt;
&lt;td&gt;Purchase&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;20&lt;/td&gt;
&lt;td&gt;&lt;a class="reference external" href="https://www.fangamer.com/collections/undertale/products/undertale-mercy-or-fight-enamel-pin-set"&gt;Fight Button — Undertale&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;2023-03-23&lt;/td&gt;
&lt;td&gt;Fangamer stand at PAX East 23&lt;/td&gt;
&lt;td&gt;Purchase&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;21&lt;/td&gt;
&lt;td&gt;&lt;a class="reference external" href="https://www.fangamer.com/collections/stardew-valley/products/pinverse-junimos-pin-pack"&gt;Junimo Package — Stardew Valley&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;2023-03-23&lt;/td&gt;
&lt;td&gt;Fangamer stand at PAX East 23&lt;/td&gt;
&lt;td&gt;Purchase&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;22&lt;/td&gt;
&lt;td&gt;&lt;a class="reference external" href="https://pinnydb.com/pinDetail/449"&gt;? Block — Super Mario Bros.&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;2023-03-23&lt;/td&gt;
&lt;td&gt;PAX merch stand at PAX East 23&lt;/td&gt;
&lt;td&gt;Purchase&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;23&lt;/td&gt;
&lt;td&gt;&lt;a class="reference external" href="https://pinnydb.com/pinDetail/446"&gt;Mario — Super Mario Bros.&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;2023-03-23&lt;/td&gt;
&lt;td&gt;PAX merch stand at PAX East 23&lt;/td&gt;
&lt;td&gt;Purchase&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;24&lt;/td&gt;
&lt;td&gt;&lt;a class="reference external" href="https://pinnydb.com/pinDetail/1362"&gt;East 2023 Logo&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;2023-03-23&lt;/td&gt;
&lt;td&gt;PAX merch stand at PAX East 23&lt;/td&gt;
&lt;td&gt;Purchase&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;25&lt;/td&gt;
&lt;td&gt;&lt;a class="reference external" href="https://pinnydb.com/pinDetail/1365"&gt;PAX East VHS&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;2023-03-23&lt;/td&gt;
&lt;td&gt;PAX merch stand at PAX East 23&lt;/td&gt;
&lt;td&gt;Purchase&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;26&lt;/td&gt;
&lt;td&gt;&lt;a class="reference external" href="https://www.neomerch.com/item.php?id=1294"&gt;Babaa — Neopets&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;2023-03-24&lt;/td&gt;
&lt;td&gt;Geekify stand at PAX East 23&lt;/td&gt;
&lt;td&gt;Purchase&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;27&lt;/td&gt;
&lt;td&gt;&lt;a class="reference external" href="https://www.neomerch.com/item.php?id=2651"&gt;Wraith Paintbrush — Neopets&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;2023-03-24&lt;/td&gt;
&lt;td&gt;Geekify stand at PAX East 23&lt;/td&gt;
&lt;td&gt;Purchase&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;28&lt;/td&gt;
&lt;td&gt;&lt;a class="reference external" href="https://www.neomerch.com/item.php?id=1287"&gt;Strawberry Fields Forever Paintbrush — Neopets&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;2023-03-24&lt;/td&gt;
&lt;td&gt;Geekify stand at PAX East 23&lt;/td&gt;
&lt;td&gt;Purchase&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;29&lt;/td&gt;
&lt;td&gt;&lt;a class="reference external" href="https://pinnydb.com/pinDetail/279"&gt;Raz — Psychonauts&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;2023-03-24..26&lt;/td&gt;
&lt;td&gt;Trading hall at PAX East 23&lt;/td&gt;
&lt;td&gt;Gift&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;30&lt;/td&gt;
&lt;td&gt;&lt;a class="reference external" href="https://pinnydb.com/pinDetail/1106"&gt;Kerbal Space Program&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;2023-03-24..26&lt;/td&gt;
&lt;td&gt;Trading hall at PAX East 23&lt;/td&gt;
&lt;td&gt;Gift&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;31&lt;/td&gt;
&lt;td&gt;&lt;a class="reference external" href="https://pinnydb.com/pinDetail/271"&gt;Atlas And P-Body — Portal 2&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;2023-03-24..26&lt;/td&gt;
&lt;td&gt;Trading hall at PAX East 23&lt;/td&gt;
&lt;td&gt;Trade&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;32&lt;/td&gt;
&lt;td&gt;&lt;a class="reference external" href="https://pinnydb.com/pinDetail/276"&gt;Diamond Ore Block — Minecraft&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;2023-03-24..26&lt;/td&gt;
&lt;td&gt;Trading hall at PAX East 23&lt;/td&gt;
&lt;td&gt;Trade&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;33&lt;/td&gt;
&lt;td&gt;&lt;a class="reference external" href="https://pinnydb.com/pinDetail/703"&gt;Mae — Night In The Woods&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;2023-03-24..26&lt;/td&gt;
&lt;td&gt;Finji stand at PAX East 23&lt;/td&gt;
&lt;td&gt;Purchase&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;34&lt;/td&gt;
&lt;td&gt;&lt;a class="reference external" href="https://pinnydb.com/pinDetail/921"&gt;Green Flame — Acquisitions Inc.&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;2023-03-24..26&lt;/td&gt;
&lt;td&gt;Acquisitions Incorporated stand at PAX East 23&lt;/td&gt;
&lt;td&gt;Purchase&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;35&lt;/td&gt;
&lt;td&gt;&lt;a class="reference external" href="https://www.fangamer.com/products/raz-psychonauts-pin-spinning"&gt;Psychokinetic Raz — Psychonauts 2&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;2023-03-24..26&lt;/td&gt;
&lt;td&gt;Fangamer stand at PAX East 23&lt;/td&gt;
&lt;td&gt;Purchase&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;36&lt;/td&gt;
&lt;td&gt;Goose — Kickstarter&lt;/td&gt;
&lt;td&gt;2023-03-24..26&lt;/td&gt;
&lt;td&gt;Kickstarter stand at PAX East 23&lt;/td&gt;
&lt;td&gt;Reward&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;37&lt;/td&gt;
&lt;td&gt;&lt;a class="reference external" href="https://pinnydb.com/pinDetail/1360"&gt;PAX Gold Mixtape&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;2023-03-24..26&lt;/td&gt;
&lt;td&gt;Carnival wheel at Pinny Arcade booth at PAX East 23&lt;/td&gt;
&lt;td&gt;Reward&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;38&lt;/td&gt;
&lt;td&gt;Portuguese Tile&lt;/td&gt;
&lt;td&gt;2023-05-06..07&lt;/td&gt;
&lt;td&gt;Portugal (Porto?)&lt;/td&gt;
&lt;td&gt;Purchase&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;39&lt;/td&gt;
&lt;td&gt;Software Freedom Conservancy's Logo&lt;/td&gt;
&lt;td&gt;2023-05-08..11&lt;/td&gt;
&lt;td&gt;Collabora Meetup in Portugal&lt;/td&gt;
&lt;td&gt;Gift (from Padovan)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;40&lt;/td&gt;
&lt;td&gt;Collabora's Logo&lt;/td&gt;
&lt;td&gt;2023-05-08..11&lt;/td&gt;
&lt;td&gt;Collabora Meetup in Portugal&lt;/td&gt;
&lt;td&gt;Gift (from Guy)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;41&lt;/td&gt;
&lt;td&gt;&lt;a class="reference external" href="https://www.fangamer.com/products/dwarf-fortress-pin-finely-crafted"&gt;+Finely-Crafted Dwarven Pin+ — Dwarf Fortress&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;2023-05-20&lt;/td&gt;
&lt;td&gt;Online&lt;/td&gt;
&lt;td&gt;Purchase&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;42&lt;/td&gt;
&lt;td&gt;Fairly Oddparents&lt;/td&gt;
&lt;td&gt;2023-06-06&lt;/td&gt;
&lt;td&gt;Online&lt;/td&gt;
&lt;td&gt;Purchase&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;43&lt;/td&gt;
&lt;td&gt;Loto&lt;/td&gt;
&lt;td&gt;2023-11-09..11&lt;/td&gt;
&lt;td&gt;Home in NYC&lt;/td&gt;
&lt;td&gt;Gift (from Tony)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;44&lt;/td&gt;
&lt;td&gt;Ecuador&lt;/td&gt;
&lt;td&gt;2023-11-09..11&lt;/td&gt;
&lt;td&gt;Home in NYC&lt;/td&gt;
&lt;td&gt;Gift (from Tony)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;45&lt;/td&gt;
&lt;td&gt;Los Andes&lt;/td&gt;
&lt;td&gt;2023-11-09..11&lt;/td&gt;
&lt;td&gt;Home in NYC&lt;/td&gt;
&lt;td&gt;Gift (from Tony)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;46&lt;/td&gt;
&lt;td&gt;Canada Wilderness&lt;/td&gt;
&lt;td&gt;2023-11-09..11&lt;/td&gt;
&lt;td&gt;CN Tower in Toronto / Home in NYC&lt;/td&gt;
&lt;td&gt;Gift (from Tony)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;47&lt;/td&gt;
&lt;td&gt;Paranaguá Train&lt;/td&gt;
&lt;td&gt;2023-11-09..11&lt;/td&gt;
&lt;td&gt;Home in NYC&lt;/td&gt;
&lt;td&gt;Gift (from Tony)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;48&lt;/td&gt;
&lt;td&gt;Wonderville Arcade Machine&lt;/td&gt;
&lt;td&gt;2023-11-10&lt;/td&gt;
&lt;td&gt;Wonderville in NYC&lt;/td&gt;
&lt;td&gt;Purchase&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;49&lt;/td&gt;
&lt;td&gt;Mask — Mr. Robot&lt;/td&gt;
&lt;td&gt;2023-11-15&lt;/td&gt;
&lt;td&gt;Online&lt;/td&gt;
&lt;td&gt;Purchase&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;50&lt;/td&gt;
&lt;td&gt;&lt;a class="reference external" href="https://www.zenmonkeystudios.com/products/squidward-at-work-spongebob-squarepants-pin"&gt;Squidward At Work — Spongebob Squarepants&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;2023-12-01&lt;/td&gt;
&lt;td&gt;Home in NYC&lt;/td&gt;
&lt;td&gt;Gift (from Mom)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;51&lt;/td&gt;
&lt;td&gt;Penguin&lt;/td&gt;
&lt;td&gt;2023-12-01&lt;/td&gt;
&lt;td&gt;Home in NYC&lt;/td&gt;
&lt;td&gt;Gift (from Mom)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;52&lt;/td&gt;
&lt;td&gt;Outback &amp;#64;&lt;/td&gt;
&lt;td&gt;2023-12-03 (originally ~2010)&lt;/td&gt;
&lt;td&gt;Home in São Paulo&lt;/td&gt;
&lt;td&gt;Gift (from Mom)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;53&lt;/td&gt;
&lt;td&gt;Outback Steakhouse Sign&lt;/td&gt;
&lt;td&gt;2023-12-03 (originally ~2010)&lt;/td&gt;
&lt;td&gt;Home in São Paulo&lt;/td&gt;
&lt;td&gt;Gift (from Mom)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;54&lt;/td&gt;
&lt;td&gt;Paper Boat&lt;/td&gt;
&lt;td&gt;2023-12-09..15&lt;/td&gt;
&lt;td&gt;Cruise to northeast of Brazil&lt;/td&gt;
&lt;td&gt;Purchase&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;55&lt;/td&gt;
&lt;td&gt;Magic The Gathering Card Back&lt;/td&gt;
&lt;td&gt;2023-12-15..2024-01-27&lt;/td&gt;
&lt;td&gt;SoGo Plaza Shopping in São Paulo&lt;/td&gt;
&lt;td&gt;Purchase&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;56&lt;/td&gt;
&lt;td&gt;Odish — Pokémon&lt;/td&gt;
&lt;td&gt;2023-12-15..2024-01-27&lt;/td&gt;
&lt;td&gt;SoGo Plaza Shopping in São Paulo&lt;/td&gt;
&lt;td&gt;Purchase&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;57&lt;/td&gt;
&lt;td&gt;&lt;a class="reference external" href="https://topatoco.com/collections/pins-and-patches/products/kcg-thisfine-pins?variant=1867673042959"&gt;This Is Fine (First Panel)&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;2024-05-29&lt;/td&gt;
&lt;td&gt;Online&lt;/td&gt;
&lt;td&gt;Purchase&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;58&lt;/td&gt;
&lt;td&gt;&lt;a class="reference external" href="https://topatoco.com/products/cpb-wtnv-pins01?_pos=2&amp;amp;_sid=ba14eae20&amp;amp;_ss=r&amp;amp;variant=30297717964911"&gt;All Hail The Glow Cloud — Welcome to Night Vale&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;2024-05-29&lt;/td&gt;
&lt;td&gt;Online&lt;/td&gt;
&lt;td&gt;Purchase&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;59&lt;/td&gt;
&lt;td&gt;&lt;a class="reference external" href="https://topatoco.com/products/won-pins-01?variant=31246520451183"&gt;Spelcheck&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;2024-05-29&lt;/td&gt;
&lt;td&gt;Online&lt;/td&gt;
&lt;td&gt;Purchase&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;60&lt;/td&gt;
&lt;td&gt;Donut&lt;/td&gt;
&lt;td&gt;2024-06-01..2024-08-01&lt;/td&gt;
&lt;td&gt;Gift shop at Bryant Park&lt;/td&gt;
&lt;td&gt;Purchase&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;61&lt;/td&gt;
&lt;td&gt;&lt;a class="reference external" href="https://www.pintrill.com/collections/peanuts/products/peanuts-originals-schroeder-pin"&gt;Schroeder — Peanuts&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;2024-06-01..2024-08-01&lt;/td&gt;
&lt;td&gt;Newtown HQ store in NYC&lt;/td&gt;
&lt;td&gt;Purchase&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;62&lt;/td&gt;
&lt;td&gt;New York Pidgeon&lt;/td&gt;
&lt;td&gt;2024-06-01..2024-08-01&lt;/td&gt;
&lt;td&gt;Newtown HQ store in NYC&lt;/td&gt;
&lt;td&gt;Purchase&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;63&lt;/td&gt;
&lt;td&gt;New York Sewer&lt;/td&gt;
&lt;td&gt;2024-06-01..2024-08-01&lt;/td&gt;
&lt;td&gt;Newtown HQ store in NYC&lt;/td&gt;
&lt;td&gt;Purchase&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;64&lt;/td&gt;
&lt;td&gt;Groundon — Pokémon&lt;/td&gt;
&lt;td&gt;2024-06-01..2024-08-01&lt;/td&gt;
&lt;td&gt;Say Cheese store in NYC&lt;/td&gt;
&lt;td&gt;Purchase&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;65&lt;/td&gt;
&lt;td&gt;Pikachu — Pokémon&lt;/td&gt;
&lt;td&gt;2024-06-01..2024-08-01&lt;/td&gt;
&lt;td&gt;Say Cheese store in NYC&lt;/td&gt;
&lt;td&gt;Purchase&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;66&lt;/td&gt;
&lt;td&gt;MTA Logo&lt;/td&gt;
&lt;td&gt;2024-08-16&lt;/td&gt;
&lt;td&gt;New York Transit Museum&lt;/td&gt;
&lt;td&gt;Purchase&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;67&lt;/td&gt;
&lt;td&gt;Dollyinho&lt;/td&gt;
&lt;td&gt;2024-09-21&lt;/td&gt;
&lt;td&gt;Vienna, Austria&lt;/td&gt;
&lt;td&gt;Gift (from Tony)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;68&lt;/td&gt;
&lt;td&gt;&lt;a class="reference external" href="https://www.zenmonkeystudios.com/collections/spongebob-squarepants/products/imagination-spongebob-squarepants-pin"&gt;Imagination! — Spongebob Squarepants&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;2024-10-17&lt;/td&gt;
&lt;td&gt;Zen Monkey Studios stand at New York Comic Con 2024&lt;/td&gt;
&lt;td&gt;Purchase&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;69&lt;/td&gt;
&lt;td&gt;&lt;a class="reference external" href="https://www.zenmonkeystudios.com/collections/spongebob-squarepants/products/mr-krabs-worlds-smallest-violin-spongebob-squarepants-pin"&gt;Mr. Krabs' World's Smallest Violin — Spongebob Squarepants&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;2024-10-17&lt;/td&gt;
&lt;td&gt;Zen Monkey Studios stand at New York Comic Con 2024&lt;/td&gt;
&lt;td&gt;Purchase&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;70&lt;/td&gt;
&lt;td&gt;&lt;a class="reference external" href="https://www.zenmonkeystudios.com/collections/spongebob-squarepants/products/nail-in-head-patrick-spongebob-squarepants-pin"&gt;Nail in Head Patrick — Spongebob Squarepants&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;2024-10-17&lt;/td&gt;
&lt;td&gt;Zen Monkey Studios stand at New York Comic Con 2024&lt;/td&gt;
&lt;td&gt;Purchase&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;71&lt;/td&gt;
&lt;td&gt;&lt;a class="reference external" href="https://www.zenmonkeystudios.com/collections/spongebob-squarepants/products/shocked-patrick-spongebob-squarepants-pin"&gt;Shocked Patrick — Spongebob Squarepants&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;2024-10-17&lt;/td&gt;
&lt;td&gt;Zen Monkey Studios stand at New York Comic Con 2024&lt;/td&gt;
&lt;td&gt;Purchase&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;72&lt;/td&gt;
&lt;td&gt;&lt;a class="reference external" href="https://www.zenmonkeystudios.com/products/speedy-sonic-classic-sonic-the-hedgehog-collectible-pin"&gt;Speedy Sonic — Classic Sonic The Hedgehog&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;2024-10-17&lt;/td&gt;
&lt;td&gt;Zen Monkey Studios stand at New York Comic Con 2024&lt;/td&gt;
&lt;td&gt;Purchase&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;73&lt;/td&gt;
&lt;td&gt;&lt;a class="reference external" href="https://www.zenmonkeystudios.com/products/pizza-drooling-gir-invader-zim-pin"&gt;Pizza Slurpin' Gir — Invader Zim&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;2024-10-17&lt;/td&gt;
&lt;td&gt;Zen Monkey Studios stand at New York Comic Con 2024&lt;/td&gt;
&lt;td&gt;Purchase&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;74&lt;/td&gt;
&lt;td&gt;&lt;a class="reference external" href="https://www.zenmonkeystudios.com/products/aang-on-air-scooter-avatar-the-last-airbender-pin"&gt;Aang On Air Scooter — Avatar: The Last Airbender&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;2024-10-17&lt;/td&gt;
&lt;td&gt;Zen Monkey Studios stand at New York Comic Con 2024&lt;/td&gt;
&lt;td&gt;Purchase&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;75&lt;/td&gt;
&lt;td&gt;Mr. Incredible — The Incredibles&lt;/td&gt;
&lt;td&gt;2024-10-17&lt;/td&gt;
&lt;td&gt;Disney pin trading event at New York Comic Con 2024&lt;/td&gt;
&lt;td&gt;Trade&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;76&lt;/td&gt;
&lt;td&gt;&lt;a class="reference external" href="https://www.pintradingdb.com/pin/58254"&gt;Woody, Buzz Lightyear &amp;amp; Jessie — Toy Story&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;2024-10-17&lt;/td&gt;
&lt;td&gt;Pins stand at New York Comic Con 2024&lt;/td&gt;
&lt;td&gt;Purchase&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;77&lt;/td&gt;
&lt;td&gt;&lt;a class="reference external" href="https://www.pintradingdb.com/pin/52091"&gt;Randall Boggs with Scream Canister — Monsters Inc.&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;2024-10-17&lt;/td&gt;
&lt;td&gt;Pins stand at New York Comic Con 2024&lt;/td&gt;
&lt;td&gt;Purchase&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;78&lt;/td&gt;
&lt;td&gt;The One Ring — The Lord of the Rings&lt;/td&gt;
&lt;td&gt;2024-10-17&lt;/td&gt;
&lt;td&gt;Pins stand at New York Comic Con 2024&lt;/td&gt;
&lt;td&gt;Purchase&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;79&lt;/td&gt;
&lt;td&gt;&lt;a class="reference external" href="https://numskull.com/products/official-pac-man-9-pin-set"&gt;Pacman&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;2024-10-17&lt;/td&gt;
&lt;td&gt;Pins stand at New York Comic Con 2024&lt;/td&gt;
&lt;td&gt;Purchase&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;80&lt;/td&gt;
&lt;td&gt;&lt;a class="reference external" href="https://www.pintradingdb.com/pin/56930"&gt;Chicken Little&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;2024-10-17&lt;/td&gt;
&lt;td&gt;Pins stand at New York Comic Con 2024&lt;/td&gt;
&lt;td&gt;Purchase&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;81&lt;/td&gt;
&lt;td&gt;&lt;a class="reference external" href="https://www.hottopic.com/product/kirby-enamel-pin-set/33449656.html"&gt;Warp Star Kirby&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;2024-10-17&lt;/td&gt;
&lt;td&gt;Pins stand at New York Comic Con 2024&lt;/td&gt;
&lt;td&gt;Purchase&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;82&lt;/td&gt;
&lt;td&gt;&lt;a class="reference external" href="https://www.hottopic.com/product/kirby-enamel-pin-set/33449656.html"&gt;Kirby&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;2024-10-17&lt;/td&gt;
&lt;td&gt;Pins stand at New York Comic Con 2024&lt;/td&gt;
&lt;td&gt;Purchase&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;83&lt;/td&gt;
&lt;td&gt;Coraline&lt;/td&gt;
&lt;td&gt;2024-10-17&lt;/td&gt;
&lt;td&gt;Spooksieboo stand at New York Comic Con 2024&lt;/td&gt;
&lt;td&gt;Purchase&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;84&lt;/td&gt;
&lt;td&gt;BFF Frog&lt;/td&gt;
&lt;td&gt;2024-10-17&lt;/td&gt;
&lt;td&gt;NYCC merch stand at New York Comic Con 2024&lt;/td&gt;
&lt;td&gt;Purchase&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;85&lt;/td&gt;
&lt;td&gt;BFF Rat&lt;/td&gt;
&lt;td&gt;2024-10-17&lt;/td&gt;
&lt;td&gt;NYCC merch stand at New York Comic Con 2024&lt;/td&gt;
&lt;td&gt;Purchase&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;86&lt;/td&gt;
&lt;td&gt;NYCC&lt;/td&gt;
&lt;td&gt;2024-10-17&lt;/td&gt;
&lt;td&gt;NYCC merch stand at New York Comic Con 2024&lt;/td&gt;
&lt;td&gt;Purchase&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;87&lt;/td&gt;
&lt;td&gt;&lt;a class="reference external" href="https://www.etsy.com/listing/905102480/ouroboros-symbol-aluminum-metal-resin"&gt;Ouroboros — Fullmetal Alchemist&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;2024-10-26&lt;/td&gt;
&lt;td&gt;Online&lt;/td&gt;
&lt;td&gt;Purchase&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;88&lt;/td&gt;
&lt;td&gt;&lt;a class="reference external" href="https://www.gallerynucleus.com/detail/23589/"&gt;Greg — Over The Garden Wall&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;2024-10-28&lt;/td&gt;
&lt;td&gt;Online&lt;/td&gt;
&lt;td&gt;Purchase&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;89&lt;/td&gt;
&lt;td&gt;Spectacle Logo&lt;/td&gt;
&lt;td&gt;2024-11-02&lt;/td&gt;
&lt;td&gt;Spectacle Theater in NYC&lt;/td&gt;
&lt;td&gt;Purchase&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;90&lt;/td&gt;
&lt;td&gt;Crash Bandicoot&lt;/td&gt;
&lt;td&gt;2024-12-10&lt;/td&gt;
&lt;td&gt;Uber ride headed to trampoline park in São Paulo&lt;/td&gt;
&lt;td&gt;Gift (from Marianna)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;91&lt;/td&gt;
&lt;td&gt;Black Cat In Bucket&lt;/td&gt;
&lt;td&gt;2024-12-10&lt;/td&gt;
&lt;td&gt;Uber ride headed to trampoline park in São Paulo&lt;/td&gt;
&lt;td&gt;Gift (from Marianna)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;92&lt;/td&gt;
&lt;td&gt;Zé Gotinha&lt;/td&gt;
&lt;td&gt;2025-01-12&lt;/td&gt;
&lt;td&gt;Tony’s home&lt;/td&gt;
&lt;td&gt;Gift (from Tony)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;93&lt;/td&gt;
&lt;td&gt;Tudor Rose&lt;/td&gt;
&lt;td&gt;2025-01-18&lt;/td&gt;
&lt;td&gt;Tony’s home&lt;/td&gt;
&lt;td&gt;Gift (from Tony)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;94&lt;/td&gt;
&lt;td&gt;&lt;a class="reference external" href="https://www.icebrg.com.br/irmao"&gt;Irmão Do Jorel&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;2025-01-20&lt;/td&gt;
&lt;td&gt;Santa Hell store in Galeria do Rock in São Paulo&lt;/td&gt;
&lt;td&gt;Purchase&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;95&lt;/td&gt;
&lt;td&gt;&lt;a class="reference external" href="https://www.icebrg.com.br/coco"&gt;Coco Mágico — Irmão do Jorel&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;2025-01-20&lt;/td&gt;
&lt;td&gt;Santa Hell store in Galeria do Rock in São Paulo&lt;/td&gt;
&lt;td&gt;Purchase&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;96&lt;/td&gt;
&lt;td&gt;&lt;a class="reference external" href="https://www.icebrg.com.br/1963"&gt;Mônica 1963 — Turma da Mônica&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;2025-01-20&lt;/td&gt;
&lt;td&gt;Santa Hell store in Galeria do Rock in São Paulo&lt;/td&gt;
&lt;td&gt;Purchase&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;97&lt;/td&gt;
&lt;td&gt;&lt;a class="reference external" href="https://www.icebrg.com.br/sansao"&gt;Sansão — Turma da Mônica&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;2025-01-20&lt;/td&gt;
&lt;td&gt;Santa Hell store in Galeria do Rock in São Paulo&lt;/td&gt;
&lt;td&gt;Purchase&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;98&lt;/td&gt;
&lt;td&gt;&lt;a class="reference external" href="https://www.icebrg.com.br/ratinho"&gt;Ratinho — Castelo Rá Tim Bum&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;2025-01-20&lt;/td&gt;
&lt;td&gt;Santa Hell store in Galeria do Rock in São Paulo&lt;/td&gt;
&lt;td&gt;Purchase&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;99&lt;/td&gt;
&lt;td&gt;&lt;a class="reference external" href="https://www.icebrg.com.br/senta"&gt;Senta Que Lá Vem História — Castelo Rá Tim Bum&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;2025-01-20&lt;/td&gt;
&lt;td&gt;Santa Hell store in Galeria do Rock in São Paulo&lt;/td&gt;
&lt;td&gt;Purchase&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;100&lt;/td&gt;
&lt;td&gt;&lt;a class="reference external" href="https://www.icebrg.com.br/minhocao"&gt;Minhocão&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;2025-01-20&lt;/td&gt;
&lt;td&gt;Santa Hell store in Galeria do Rock in São Paulo&lt;/td&gt;
&lt;td&gt;Purchase&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;101&lt;/td&gt;
&lt;td&gt;&lt;a class="reference external" href="https://www.icebrg.com.br/liberdade"&gt;Liberdade&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;2025-01-20&lt;/td&gt;
&lt;td&gt;Santa Hell store in Galeria do Rock in São Paulo&lt;/td&gt;
&lt;td&gt;Purchase&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;102&lt;/td&gt;
&lt;td&gt;&lt;a class="reference external" href="https://www.icebrg.com.br/cavaquinho"&gt;Cavaquinho&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;2025-01-20&lt;/td&gt;
&lt;td&gt;Santa Hell store in Galeria do Rock in São Paulo&lt;/td&gt;
&lt;td&gt;Purchase&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;103&lt;/td&gt;
&lt;td&gt;&lt;a class="reference external" href="https://www.icebrg.com.br/saojorge"&gt;São Jorge&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;2025-01-20&lt;/td&gt;
&lt;td&gt;Santa Hell store in Galeria do Rock in São Paulo&lt;/td&gt;
&lt;td&gt;Purchase&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;104&lt;/td&gt;
&lt;td&gt;&lt;a class="reference external" href="https://www.icebrg.com.br/capivara"&gt;Capivara&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;2025-01-20&lt;/td&gt;
&lt;td&gt;Santa Hell store in Galeria do Rock in São Paulo&lt;/td&gt;
&lt;td&gt;Purchase&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;105&lt;/td&gt;
&lt;td&gt;&lt;a class="reference external" href="https://www.icebrg.com.br/piso"&gt;Piso de Caquinho&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;2025-01-20&lt;/td&gt;
&lt;td&gt;Santa Hell store in Galeria do Rock in São Paulo&lt;/td&gt;
&lt;td&gt;Purchase&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;106&lt;/td&gt;
&lt;td&gt;&lt;a class="reference external" href="https://www.icebrg.com.br/luckycat"&gt;Lucky Cat&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;2025-01-30&lt;/td&gt;
&lt;td&gt;Online&lt;/td&gt;
&lt;td&gt;Purchase&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;107&lt;/td&gt;
&lt;td&gt;&lt;a class="reference external" href="https://www.icebrg.com.br/andorinha"&gt;Andorinha&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;2025-01-30&lt;/td&gt;
&lt;td&gt;Online&lt;/td&gt;
&lt;td&gt;Purchase&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;108&lt;/td&gt;
&lt;td&gt;&lt;a class="reference external" href="https://www.icebrg.com.br/fliper"&gt;Fliperama&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;2025-01-30&lt;/td&gt;
&lt;td&gt;Online&lt;/td&gt;
&lt;td&gt;Purchase&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;109&lt;/td&gt;
&lt;td&gt;&lt;a class="reference external" href="https://www.icebrg.com.br/bilhete"&gt;Metrô SP&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;2025-01-30&lt;/td&gt;
&lt;td&gt;Online&lt;/td&gt;
&lt;td&gt;Purchase&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;110&lt;/td&gt;
&lt;td&gt;&lt;a class="reference external" href="https://www.icebrg.com.br/augusta"&gt;Rua Augusta&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;2025-01-30&lt;/td&gt;
&lt;td&gt;Online&lt;/td&gt;
&lt;td&gt;Purchase&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;111&lt;/td&gt;
&lt;td&gt;&lt;a class="reference external" href="https://www.icebrg.com.br/brasil"&gt;Bandeira Brasil&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;2025-01-30&lt;/td&gt;
&lt;td&gt;Online&lt;/td&gt;
&lt;td&gt;Purchase&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;112&lt;/td&gt;
&lt;td&gt;Tv Cultura&lt;/td&gt;
&lt;td&gt;2025-02-02&lt;/td&gt;
&lt;td&gt;Street fair close to Av. Paulista in São Paulo&lt;/td&gt;
&lt;td&gt;Purchase&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;113&lt;/td&gt;
&lt;td&gt;Globo&lt;/td&gt;
&lt;td&gt;2025-02-02&lt;/td&gt;
&lt;td&gt;Street fair close to Av. Paulista in São Paulo&lt;/td&gt;
&lt;td&gt;Purchase&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;114&lt;/td&gt;
&lt;td&gt;Linux Conectiva&lt;/td&gt;
&lt;td&gt;2025-02-02&lt;/td&gt;
&lt;td&gt;Street fair close to Av. Paulista in São Paulo&lt;/td&gt;
&lt;td&gt;Purchase&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;115&lt;/td&gt;
&lt;td&gt;Windows 2000&lt;/td&gt;
&lt;td&gt;2025-02-02&lt;/td&gt;
&lt;td&gt;Street fair close to Av. Paulista in São Paulo&lt;/td&gt;
&lt;td&gt;Purchase&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;116&lt;/td&gt;
&lt;td&gt;ABNT&lt;/td&gt;
&lt;td&gt;2025-02-02&lt;/td&gt;
&lt;td&gt;Street fair close to Av. Paulista in São Paulo&lt;/td&gt;
&lt;td&gt;Purchase&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;117&lt;/td&gt;
&lt;td&gt;&lt;a class="reference external" href="https://www.etsy.com/listing/4302174034/daft-punk-pin-enamel-lapel-alive"&gt;Daft Punk&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;2025-03-18&lt;/td&gt;
&lt;td&gt;Online&lt;/td&gt;
&lt;td&gt;Purchase&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;118&lt;/td&gt;
&lt;td&gt;&lt;a class="reference external" href="https://www.etsy.com/listing/1030452012/chrono-trigger-for-snes-chrono-trigger"&gt;Chrono Trigger&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;2025-03-27&lt;/td&gt;
&lt;td&gt;Online&lt;/td&gt;
&lt;td&gt;Purchase&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;119&lt;/td&gt;
&lt;td&gt;&lt;a class="reference external" href="https://www.etsy.com/listing/1724710918/sims-plumbob-mini-enamel-pin"&gt;Plumbob — The Sims&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;2025-03-28&lt;/td&gt;
&lt;td&gt;Online&lt;/td&gt;
&lt;td&gt;Purchase&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;120&lt;/td&gt;
&lt;td&gt;&lt;a class="reference external" href="https://www.etsy.com/listing/1570559383/cardcaptor-sealing-key-mini-enamel-pin"&gt;Cardcaptor Sealing Key — Cardcaptor Sakura&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;2025-03-28&lt;/td&gt;
&lt;td&gt;Online&lt;/td&gt;
&lt;td&gt;Purchase&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;121&lt;/td&gt;
&lt;td&gt;&lt;a class="reference external" href="https://www.etsy.com/listing/1570493833/triforce-mini-enamel-pin"&gt;Triforce — Zelda&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;2025-03-28&lt;/td&gt;
&lt;td&gt;Online&lt;/td&gt;
&lt;td&gt;Purchase&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;122&lt;/td&gt;
&lt;td&gt;&lt;a class="reference external" href="https://www.etsy.com/listing/1608102952/red-cloud-mini-enamel-pin"&gt;Akatsuki — Naruto&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;2025-03-28&lt;/td&gt;
&lt;td&gt;Online&lt;/td&gt;
&lt;td&gt;Purchase&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;123&lt;/td&gt;
&lt;td&gt;&lt;a class="reference external" href="https://www.etsy.com/listing/1516361510/nostalgic-video-game-characters-pins-vol"&gt;Klonoa&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;2025-03-28&lt;/td&gt;
&lt;td&gt;Online&lt;/td&gt;
&lt;td&gt;Purchase&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;124&lt;/td&gt;
&lt;td&gt;&lt;a class="reference external" href="https://www.compressmerch.com/collections/finji/products/love_from_wilmot_pinny"&gt;Love From Wilmot — Wilmot’s Warehouse&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;2025-04-26&lt;/td&gt;
&lt;td&gt;Online&lt;/td&gt;
&lt;td&gt;Purchase&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;125&lt;/td&gt;
&lt;td&gt;&lt;a class="reference external" href="https://www.compressmerch.com/products/usual-june-pinny-arcade-enamel-pin?variant=44932087873698"&gt;Usual June&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;2025-04-26&lt;/td&gt;
&lt;td&gt;Online&lt;/td&gt;
&lt;td&gt;Purchase&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;126&lt;/td&gt;
&lt;td&gt;Hope Hammer — Godspeed You! Black Emperor&lt;/td&gt;
&lt;td&gt;2025-06-27&lt;/td&gt;
&lt;td&gt;Godspeed You Black Emperor show at District Music Hall in Norwalk, CT&lt;/td&gt;
&lt;td&gt;Purchase&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;127&lt;/td&gt;
&lt;td&gt;Javascript&lt;/td&gt;
&lt;td&gt;2025-08-07&lt;/td&gt;
&lt;td&gt;Astoria Tech Meetup meeting&lt;/td&gt;
&lt;td&gt;Gift (from Tea)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;128&lt;/td&gt;
&lt;td&gt;&lt;a class="reference external" href="https://www.etsy.com/listing/1780904874/fez-gomez-1-enamel-pin-and-magnet"&gt;Gomez — FEZ&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;2025-08-09&lt;/td&gt;
&lt;td&gt;Long Island Retro Gaming Expo at Cradle of Aviation Museum&lt;/td&gt;
&lt;td&gt;Purchase&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;[*] The Collabora's Logo pin is not in the display since it's magnetic, but you
can see it in the previous blog post.&lt;/p&gt;
&lt;p&gt;The full spreadsheet wouldn't fit comfortably in this page, so the table above
contains only the main columns. You can download the full spreadsheet from the
link below which also includes the pin's authenticity, maker and additional
comments:&lt;/p&gt;
&lt;p&gt;&lt;a class="reference external" href="https://nfraprado.net/files/pins-spreadsheet/pins.ods"&gt;Pins spreadsheet&lt;/a&gt;&lt;/p&gt;
</content><category term="2025"/><category term="pin"/></entry><entry><title>Song of the Month: Soundgarden - Black Hole Sun</title><link href="https://nfraprado.net/post/song-of-the-month-soundgarden-black-hole-sun.html" rel="alternate"/><published>2025-08-31T00:00:00-03:00</published><updated>2025-08-31T00:00:00-03:00</updated><author><name>Nícolas F. R. A. Prado</name></author><id>tag:nfraprado.net,2025-08-31:/post/song-of-the-month-soundgarden-black-hole-sun.html</id><summary type="html">&lt;p&gt;Just two days ago I went to an immersive theatrical experience called &lt;a class="reference external" href="https://www.theshed.org/program/444-violas-room"&gt;Viola's Room&lt;/a&gt;. It started in a girl's bedroom with dim lighting and a few beds made on the floor as in a sleepover. On the headphones, a beautiful song started playing. The moment was so magical it's hard …&lt;/p&gt;</summary><content type="html">&lt;p&gt;Just two days ago I went to an immersive theatrical experience called &lt;a class="reference external" href="https://www.theshed.org/program/444-violas-room"&gt;Viola's Room&lt;/a&gt;. It started in a girl's bedroom with dim lighting and a few beds made on the floor as in a sleepover. On the headphones, a beautiful song started playing. The moment was so magical it's hard to describe. I felt like a kid again for a second. And I knew I'd always remember this moment in the future (&lt;a class="reference external" href="https://www.thedictionaryofobscuresorrows.com/concept/des-vu"&gt;dès vu&lt;/a&gt;). If you have the chance to go, I deeply recommend it.&lt;/p&gt;
&lt;p&gt;Anyway, the song that was playing, as I later found out, is my song of the month for August:&lt;/p&gt;
&lt;p&gt;&lt;a class="reference external" href="https://www.youtube.com/watch?v=3mbBbFH9fAg"&gt;Soundgarden - Black Hole Sun&lt;/a&gt;&lt;/p&gt;
</content><category term="2025"/><category term="song-of-the-month"/></entry><entry><title>Song of the Month: Junko Yagami - Bay City</title><link href="https://nfraprado.net/post/song-of-the-month-junko-yagami-bay-city.html" rel="alternate"/><published>2025-08-12T00:00:00-03:00</published><updated>2025-08-12T00:00:00-03:00</updated><author><name>Nícolas F. R. A. Prado</name></author><id>tag:nfraprado.net,2025-08-12:/post/song-of-the-month-junko-yagami-bay-city.html</id><summary type="html">&lt;p&gt;Today I want to try starting something new. Every month I'll share one song that
stood out to me throughout the month.&lt;/p&gt;
&lt;p&gt;For this first one, even though it's already midway through August, I still
really feel the need to share this song that has marked my days throughout July …&lt;/p&gt;</summary><content type="html">&lt;p&gt;Today I want to try starting something new. Every month I'll share one song that
stood out to me throughout the month.&lt;/p&gt;
&lt;p&gt;For this first one, even though it's already midway through August, I still
really feel the need to share this song that has marked my days throughout July
and even June. Many of my strolls around the city on sunny days have been
accompanied by it, making it the hymn for this Summer to me.&lt;/p&gt;
&lt;p&gt;Without further ado, my song of the month for July is:&lt;/p&gt;
&lt;p&gt;&lt;a class="reference external" href="https://www.youtube.com/watch?v=QLHMhVonF-s"&gt;Junko Yagami - Bay City&lt;/a&gt;&lt;/p&gt;
</content><category term="2025"/><category term="song-of-the-month"/></entry><entry><title>A new theme for the blog (now with 0% JavaScript!)</title><link href="https://nfraprado.net/post/a-new-theme-for-the-blog-now-with-0-javascript.html" rel="alternate"/><published>2025-05-04T00:00:00-03:00</published><updated>2025-05-04T00:00:00-03:00</updated><author><name>Nícolas F. R. A. Prado</name></author><id>tag:nfraprado.net,2025-05-04:/post/a-new-theme-for-the-blog-now-with-0-javascript.html</id><summary type="html">&lt;p&gt;I've recently updated the blog's theme and I felt I should make a post not only
to document the reason but also to archive pictures of the blog before and after
the change for the future.&lt;/p&gt;
&lt;p&gt;When I created this blog, I chose &lt;a class="reference external" href="https://github.com/gunchu/nikhil-theme"&gt;nikhil-theme&lt;/a&gt; as the theme because I found …&lt;/p&gt;</summary><content type="html">&lt;p&gt;I've recently updated the blog's theme and I felt I should make a post not only
to document the reason but also to archive pictures of the blog before and after
the change for the future.&lt;/p&gt;
&lt;p&gt;When I created this blog, I chose &lt;a class="reference external" href="https://github.com/gunchu/nikhil-theme"&gt;nikhil-theme&lt;/a&gt; as the theme because I found it
minimalist and pretty. (Even prettier after I switched the fonts to be the same
as the ones in the &lt;a class="reference external" href="https://www.thirtythreeforty.net/"&gt;https://www.thirtythreeforty.net/&lt;/a&gt; blog)&lt;/p&gt;
&lt;p&gt;However, quite some time later as I was looking into the theme's template HTML
files, I was surprised to discover that this very simple theme contained
JavaScript. I tried loading the page with JavaScript removed and found
that it was only required to get the hamburger menu to expand on mobile devices.
That didn't sit well with me. A simple blog like this shouldn't require any
JavaScript at all. And I found it even more annoying that it was only required
because of one single feature.&lt;/p&gt;
&lt;p&gt;At the time I had already been making other changes to the blog and was not
looking forward to completely switching the theme, so this thought stayed in the
back of my mind until recently, when I finally decided to do something about it.&lt;/p&gt;
&lt;p&gt;In order to decide on the new theme for the blog, I started by looking through
&lt;a class="reference external" href="https://github.com/getpelican/pelican-themes"&gt;pelican-themes&lt;/a&gt; (the same place where I originally discovered nikhil-theme) but
only found one or two themes that didn't use any JavaScript and they didn't
really appeal to me.&lt;/p&gt;
&lt;p&gt;Eventually I remembered about the &lt;a class="reference external" href="https://seirdy.one/"&gt;seirdy.one&lt;/a&gt; blog. That's a blog I follow which
has excellent write-ups, and &lt;a class="reference external" href="https://seirdy.one/meta/site-design/"&gt;there's a whole page about the design standards&lt;/a&gt;
that the website adheres to. The standards focus on minimalism, compatibility
and accessibility, which are all qualities that I would like for my blog, so I
decided to adopt Seirdy's theme for my blog.&lt;/p&gt;
&lt;p&gt;Looking through &lt;a class="reference external" href="https://git.sr.ht/~seirdy/seirdy.one"&gt;seirdy blog's source code&lt;/a&gt; I noticed it uses the &lt;a class="reference external" href="https://gohugo.io/"&gt;Hugo&lt;/a&gt;
static site generator whereas my blog uses &lt;a class="reference external" href="https://getpelican.com/"&gt;pelican&lt;/a&gt;, so adapting this theme
wouldn't be just a simple copy-paste.&lt;/p&gt;
&lt;p&gt;Ideally I'd have gone through Seirdy's HTML templates and understood them all so
I could adapt them to my HTML Jinja2 templates for pelican, but I honestly
didn't have all that energy in me. So I went for the lazy solution: I copied
over the CSS files and tweaked my templates until the website looked good
enough. This means I'm probably not following many of Seirdy's standards, but at
least the website is leaner, without any JS (and even external CSS), which was
the main goal.&lt;/p&gt;
&lt;p&gt;The resulting source code change can be seen in &lt;a class="reference external" href="https://codeberg.org/nfraprado/blog/commit/4be0e1d84495f3186a0811f1d184062e63fe1f21"&gt;this commit&lt;/a&gt; (and also &lt;a class="reference external" href="https://codeberg.org/nfraprado/blog/commit/6f82039f935841dc1fc09c968c48f796528e5123"&gt;this&lt;/a&gt;
and &lt;a class="reference external" href="https://codeberg.org/nfraprado/blog/commit/ff36f3cdb1e1bd3fcb62763afccecdbf9ca31508"&gt;this&lt;/a&gt; for follow up fixes).&lt;/p&gt;
&lt;p&gt;As for the resulting changes on the website itself, the pictures below show
different pages before and after the theme change, respectively.&lt;/p&gt;
&lt;p&gt;Home page:&lt;/p&gt;
&lt;img alt="{image}/before-home.png" src="/images/blog-theme-seirdy-update/before-home.png" /&gt;
&lt;img alt="{image}/after-home.png" src="/images/blog-theme-seirdy-update/after-home.png" /&gt;
&lt;p&gt;A blog post:&lt;/p&gt;
&lt;img alt="{image}/before-post.png" src="/images/blog-theme-seirdy-update/before-post.png" /&gt;
&lt;img alt="{image}/after-post.png" src="/images/blog-theme-seirdy-update/after-post.png" /&gt;
&lt;p&gt;Tags:&lt;/p&gt;
&lt;img alt="{image}/before-tags.png" src="/images/blog-theme-seirdy-update/before-tags.png" /&gt;
&lt;img alt="{image}/after-tags.png" src="/images/blog-theme-seirdy-update/after-tags.png" /&gt;
&lt;p&gt;About:&lt;/p&gt;
&lt;img alt="{image}/before-about.png" src="/images/blog-theme-seirdy-update/before-about.png" /&gt;
&lt;img alt="{image}/after-about.png" src="/images/blog-theme-seirdy-update/after-about.png" /&gt;
&lt;p&gt;I won't lie, I already feel nostalgic and a bit sad to see the old theme go, but
I believe making the website more minimalist is worth it.&lt;/p&gt;
&lt;p&gt;One thing lost in this transition was syntax highlighting for code snippets. I
assume it's intentionally avoided by Seirdy due to accessibility concerns, but I
should confirm that at some point and if it's not the case then consider adding
it back.&lt;/p&gt;
&lt;p&gt;Even though there are still a few details left to iron out, I'm quite happy with
the change and particularly relieved there's no more JavaScript running when
seeing this blog.&lt;/p&gt;
</content><category term="2025"/><category term="blog"/></entry><entry><title>In search of the perfect pin necklace</title><link href="https://nfraprado.net/post/in-search-of-the-perfect-pin-necklace.html" rel="alternate"/><published>2024-11-16T00:00:00-03:00</published><updated>2024-11-16T00:00:00-03:00</updated><author><name>Nícolas F. R. A. Prado</name></author><id>tag:nfraprado.net,2024-11-16:/post/in-search-of-the-perfect-pin-necklace.html</id><summary type="html">&lt;p&gt;Last year, on the &lt;a class="reference external" href="/post/collecting-pins.html"&gt;"Collecting pins" post&lt;/a&gt;, I mentioned that I usually wear my pins on
a beanie, but was still looking for a good alternative for the warmer months.&lt;/p&gt;
&lt;p&gt;After thinking more about it, I realized a necklace would be the way to go.
It doesn't depend on any …&lt;/p&gt;</summary><content type="html">&lt;p&gt;Last year, on the &lt;a class="reference external" href="/post/collecting-pins.html"&gt;"Collecting pins" post&lt;/a&gt;, I mentioned that I usually wear my pins on
a beanie, but was still looking for a good alternative for the warmer months.&lt;/p&gt;
&lt;p&gt;After thinking more about it, I realized a necklace would be the way to go.
It doesn't depend on any specific piece of clothing and it's pretty visible, not
only to others but also to myself.&lt;/p&gt;
&lt;p&gt;I started researching for pin necklaces and found a few different solutions for
the pin-to-necklace converter:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;a class="reference external" href="https://www.epbot.com/2020/04/quick-craft-turn-any-disney-pin-into.html"&gt;DIY Guide: Rubber pin back converter&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference external" href="https://www.etsy.com/listing/1672516454/clearrings-necklace-converter-enamel-pin"&gt;Buy: Transparent plastic converter&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference external" href="https://www.etsy.com/listing/607824060/enamel-pin-necklace-converter-wear-your?show_sold_out_detail=1&amp;amp;ref=nla_listing_details"&gt;Buy: Leather patch converter&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference external" href="https://cosmicmedium.com/products/lapel-pin-necklace-lock-converter?variant=40404834123973"&gt;Buy: Locking pin back converter&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The guide is great, but I don't feel rubber pin backs are safe enough. And
neither the plastic nor the leather converters looked good in my opinion. But
the locking pin back one seemed perfect! So I ordered one right away.&lt;/p&gt;
&lt;p&gt;Actually, I ordered two. The reason is that some pins have two posts,
side-by-side, so two pin backs would be needed to keep those pins correctly
oriented on the necklace (see pictures at the end). By buying two necklaces I
could transfer the converter from one of them over and have a necklace with two
converters, allowing me to wear any pin in my collection.&lt;/p&gt;
&lt;p&gt;Once the necklace arrived and I started using it, I quickly realized there was a
big problem with it though: the pin kept getting turned around! That totally
beats the purpose of the necklace. I concluded the problem was the chain, which
needed to be switched for something stiff, like a leather cord. I settled on
this braided leather necklace (the 20inch in length and 3mm in diameter
variant):
&lt;a class="reference external" href="https://www.amazon.com/Flexible-Braided-Leather-Necklace-Pendants/dp/B095W7J7D7"&gt;https://www.amazon.com/Flexible-Braided-Leather-Necklace-Pendants/dp/B095W7J7D7&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Once that arrived, I took two pliers, two jump rings (since the original ones
wouldn't close around this broader necklace) and used them to put the two pin
back converters on the new necklace. And it worked perfectly, no more pins
getting turned around! I also liked the way it looked much better, the chain was
too shiny and thin for my taste.&lt;/p&gt;
&lt;p&gt;On top of that, since the jump rings I used are much broader than the necklace,
it's super easy to get the pin back converters in and out of it. That way, when
I'm wearing a pin that only uses one back, I take the other out so it's not
dangling and making noise.&lt;/p&gt;
&lt;p&gt;Finally, here are the pictures of the end result:&lt;/p&gt;
&lt;p&gt;One-post pin:&lt;/p&gt;
&lt;img alt="{image}/df-front.jpg" src="/images/pin-necklace/df-front.jpg" /&gt;
&lt;img alt="{image}/df-back.jpg" src="/images/pin-necklace/df-back.jpg" /&gt;
&lt;p&gt;Two-posts pin:&lt;/p&gt;
&lt;img alt="{image}/lotr-front.jpg" src="/images/pin-necklace/lotr-front.jpg" /&gt;
&lt;img alt="{image}/lotr-back.jpg" src="/images/pin-necklace/lotr-back.jpg" /&gt;
&lt;p&gt;Pins being worn:&lt;/p&gt;
&lt;img alt="{image}/df-wear.jpg" src="/images/pin-necklace/df-wear.jpg" /&gt;
&lt;img alt="{image}/lotr-wear.jpg" src="/images/pin-necklace/lotr-wear.jpg" /&gt;
&lt;p&gt;The only issue is that the one-post pins, like the Dwarf Fortress one above,
always rest tilted to one side. I don't think there's a way to avoid that on a
necklace though. In any case, that's a minor issue and I am incredibly happy
with the result. I've been wearing pins everyday since!&lt;/p&gt;
</content><category term="2024"/><category term="pin"/></entry><entry><title>CardOS: Now compiling without Arduino!</title><link href="https://nfraprado.net/post/cardos-now-compiling-without-arduino.html" rel="alternate"/><published>2024-07-31T00:00:00-03:00</published><updated>2024-07-31T00:00:00-03:00</updated><author><name>Nícolas F. R. A. Prado</name></author><id>tag:nfraprado.net,2024-07-31:/post/cardos-now-compiling-without-arduino.html</id><summary type="html">&lt;p&gt;In the &lt;a class="reference external" href="/post/cardos-writing-an-os-for-the-cardputer.html"&gt;"CardOS: Writing an OS for the Cardputer" post&lt;/a&gt; I shared about the OS that I'm writing for the
Cardputer and that the next step was to move away from the Arduino toolchain. It
took me two months but I finally did it. The end product was &lt;a class="reference external" href="https://codeberg.org/nfraprado/cardOS/commit/85553f797cf0877e4c88a8e549052a445d00e7d1"&gt;this commit …&lt;/a&gt;&lt;/p&gt;</summary><content type="html">&lt;p&gt;In the &lt;a class="reference external" href="/post/cardos-writing-an-os-for-the-cardputer.html"&gt;"CardOS: Writing an OS for the Cardputer" post&lt;/a&gt; I shared about the OS that I'm writing for the
Cardputer and that the next step was to move away from the Arduino toolchain. It
took me two months but I finally did it. The end product was &lt;a class="reference external" href="https://codeberg.org/nfraprado/cardOS/commit/85553f797cf0877e4c88a8e549052a445d00e7d1"&gt;this commit&lt;/a&gt; but I'd like to go over the process to
get there in this post.&lt;/p&gt;
&lt;p&gt;I started by running the Arduino commands to build and flash with verbose output
enabled and saving that output for reference. This way I could see what exactly
Arduino was doing and understand the steps needed to get from a source to a
binary flashed to the chip's memory.&lt;/p&gt;
&lt;p&gt;I then got to doing the obvious needed changes to convert from Arduino to C: I
renamed the single source of the project from &lt;code class="docutils literal"&gt;cardOS.ino&lt;/code&gt; to &lt;code class="docutils literal"&gt;cardOS.c&lt;/code&gt;,
changed the &lt;code class="docutils literal"&gt;setup&lt;/code&gt; and &lt;code class="docutils literal"&gt;loop&lt;/code&gt; functions into a &lt;code class="docutils literal"&gt;main&lt;/code&gt; function, and
changed the compilation step in my Makefile to rather than calling Arduino, call
the ESP32 C cross-compiler (&lt;code class="docutils literal"&gt;xtensa-esp32s3-elf-gcc&lt;/code&gt;), which had been
installed on my machine by Arduino and whose path I learned from Arduino's
output.&lt;/p&gt;
&lt;p&gt;Next I ran the compilation, and fixed each of the errors that were thrown by the
compiler. Namely I had to add a few includes, function prototypes, change the
way a couple variables were defined and pass the &lt;code class="docutils literal"&gt;-fno-builtin&lt;/code&gt; flag to the
compiler so it would allow me to define my own stdlib functions. With that, I
had an &lt;a class="reference external" href="https://en.wikipedia.org/wiki/Executable_and_Linkable_Format"&gt;ELF&lt;/a&gt; file and needed to figure out how to do the flashing.&lt;/p&gt;
&lt;p&gt;From looking through Arduino's output, I learned that &lt;code class="docutils literal"&gt;esptool.py&lt;/code&gt; was the
command used to convert the ELF file into a binary, and then called again to
flash the binary into the chip. Besides the application code, there were other
things being flashed: a bootloader and a partition table. In order to keep
things simple, I did a quick test to verify whether I could ignore them for now
(that is, assume they were already flashed before): I tweaked the OS code and
used the Arduino toolchain to build and flash just that and the tweak showed up
on the Cardputer, so the answer was yes.&lt;/p&gt;
&lt;p&gt;With that in mind, I updated the Makefile to call &lt;code class="docutils literal"&gt;esptool.py&lt;/code&gt; to convert the
ELF generated by the compiler and then flash it to the board. At this point I
had the whole procedure to get from the source to the flashed binary on the
board figured out (or so I thought), so I ran it with &lt;code class="docutils literal"&gt;make upload&lt;/code&gt;. But it
did not work, the screen on the cardputer just wouldn't turn on.&lt;/p&gt;
&lt;p&gt;I realized assuming all the code would just work in the new setup was too
optimistic and decided to simplify the test as much as possible: I changed the
code in &lt;code class="docutils literal"&gt;main&lt;/code&gt; to simply drive the GPIO1 pin high and connected an LED to that
pin. But even then, the LED didn't turn on.&lt;/p&gt;
&lt;p&gt;This is where I got stuck for a while. The issue was clearly somewhere in the
application binary. My two main theories were that either the binary itself was
malformed, or I was missing some initialization code, since Arduino included a
bunch of extra files during compilation.&lt;/p&gt;
&lt;div class="section" id="configuring-the-addresses-in-the-elf-file"&gt;
&lt;h2&gt;Configuring the addresses in the ELF file&lt;/h2&gt;
&lt;p&gt;Hoping the issue was in the binary itself, since identifying missing
initialization code from Arduino could take a while, I started investigating it.&lt;/p&gt;
&lt;p&gt;I used &lt;code class="docutils literal"&gt;readelf&lt;/code&gt; to see the contents of both my ELF and the one generated by
Arduino and compared them. The biggest change in the header was this:&lt;/p&gt;
&lt;p&gt;Mine:&lt;/p&gt;
&lt;pre class="code literal-block"&gt;
Entry point address:               0x40017d
&lt;/pre&gt;
&lt;p&gt;Arduino's:&lt;/p&gt;
&lt;pre class="code literal-block"&gt;
Entry point address:               0x40376778
&lt;/pre&gt;
&lt;p&gt;Arduino's ELF had much more code in it, so I expected its address to be higher,
but this was too much of a difference. It looked like some base address was
intentionally set to something different.&lt;/p&gt;
&lt;p&gt;I looked up the meaning of the &amp;quot;Entry point address&amp;quot; in an ELF and confirmed
that this is the address where execution starts from. So getting it wrong could
definitely make my code not work at all.&lt;/p&gt;
&lt;p&gt;Looking further through the ELF's content:&lt;/p&gt;
&lt;p&gt;Mine:&lt;/p&gt;
&lt;pre class="code literal-block"&gt;
Program Headers:
  Type           Offset   VirtAddr   PhysAddr   FileSiz MemSiz  Flg Align
  LOAD           0x000000 0x00400000 0x00400000 0x0183c 0x0183c R E 0x1000
  LOAD           0x00183c 0x0040283c 0x0040283c 0x001a1 0x0075c RW  0x1000

 Section to Segment mapping:
  Segment Sections...
   00     .text .rodata .eh_frame
   01     .ctors .dtors .data .bss
&lt;/pre&gt;
&lt;p&gt;Arduino's:&lt;/p&gt;
&lt;pre class="code literal-block"&gt;
Program Headers:
  Type           Offset   VirtAddr   PhysAddr   FileSiz MemSiz  Flg Align
  LOAD           0x001020 0x3c000020 0x3c000020 0x2a678 0x2a678 RW  0x1000
  LOAD           0x02c000 0x3fc88000 0x3fc88000 0x0c018 0x0d8a8 RW  0x1000
  LOAD           0x039000 0x40374000 0x40374000 0x0ca97 0x0ca98 RWE 0x1000
  LOAD           0x046020 0x42000020 0x42000020 0x1f347 0x1f347 R E 0x1000
  LOAD           0x000000 0x50000000 0x50000000 0x00000 0x00010 RW  0x1000

 Section to Segment mapping:
  Segment Sections...
   00     .flash_rodata_dummy .flash.appdesc .flash.rodata
   01     .dram0.dummy .dram0.data .dram0.bss
   02     .iram0.vectors .iram0.text .iram0.data
   03     .flash.text
   04     .rtc_noinit
&lt;/pre&gt;
&lt;p&gt;After reading up online about ELF program headers and sections this started
making sense.&lt;/p&gt;
&lt;p&gt;Looking at the program headers on the Arduino ELF, there is indeed an address
very close to the entry point address: &lt;code class="docutils literal"&gt;0x40374000&lt;/code&gt; on segment 2. That is the
base address I was suspecting. The code that starts executing then must be in
the &lt;code class="docutils literal"&gt;iram0.text&lt;/code&gt; section, since sections with &lt;code class="docutils literal"&gt;text&lt;/code&gt; in the name represent
code, and that one is mapped to segment 2. The questions that come to mind are:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;Why is this address used?&lt;/li&gt;
&lt;li&gt;How do I set this address in my ELF file?&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;To answer the first question, I went looking in the &lt;a class="reference external" href="https://www.espressif.com/sites/default/files/documentation/esp32-s3_technical_reference_manual_en.pdf"&gt;ESP32-S3 manual&lt;/a&gt;. Figure
4-1 shows a diagram with how each memory range is mapped. The starting address
of the segment used for the entry point code in Arduino, &lt;code class="docutils literal"&gt;0x40374000&lt;/code&gt;, is
inside the range &lt;code class="docutils literal"&gt;0x40370000 - 0x403dffff&lt;/code&gt; which is shown as mapped to SRAM
through an instruction bus which totally makes sense!&lt;/p&gt;
&lt;p&gt;But reading on, Table 4-1 further breaks down the memory regions and names
the region containing &lt;code class="docutils literal"&gt;0x40374000&lt;/code&gt; as &amp;quot;Internal SRAM0&amp;quot;. In its description, it
is mentioned the first 16KB of the space can be reserved as a cache for instructions
stored in the flash memory. If we do the math, that means the usable instruction
memory starts at... &lt;code class="docutils literal"&gt;0x40374000&lt;/code&gt;, exactly the address that was used for the
code segment in Arduino's ELF! So that explains where this address came from.&lt;/p&gt;
&lt;p&gt;To sum up, the problem I uncovered is that the ELF file I was generating had
the code assigned to addresses that do not map to a memory region that can be
accessed by the ESP32's processor to fetch instructions (ie accessible through
an instruction bus) and therefore it couldn't be executed.&lt;/p&gt;
&lt;p&gt;What was left was to answer the second question: How do I set the right address
in my ELF file?&lt;/p&gt;
&lt;p&gt;This was a big gap in my knowledge, I had no idea. But looking through Arduino's
output, I saw that the compiler was being passed some &lt;code class="docutils literal"&gt;.ld&lt;/code&gt; files through a
&lt;code class="docutils literal"&gt;-T&lt;/code&gt; parameter. Inside one of them called &lt;code class="docutils literal"&gt;memory.ld&lt;/code&gt; I found addresses
being defined! The &lt;code class="docutils literal"&gt;-T&lt;/code&gt; flag entry in the compiler's manual page revealed these
files were linker scripts, so I knew what I needed to learn about next.&lt;/p&gt;
&lt;p&gt;I found &lt;a class="reference external" href="https://allthingsembedded.com/post/2020-04-11-mastering-the-gnu-linker-script/"&gt;this wonderful blog post about linker scripts&lt;/a&gt; that taught me
everything I needed to know. With that information I was able to write my linker
script to assign the right addresses to each ELF section: Not only for the code
(&lt;code class="docutils literal"&gt;.text&lt;/code&gt;), but also for the zero-initialized variables (&lt;code class="docutils literal"&gt;.bss&lt;/code&gt;) and other
variables (&lt;code class="docutils literal"&gt;.data&lt;/code&gt;), whose addresses I figured out similarly by referencing
the manual and the Arduino ELF. A few additional sections also had to be
assigned to get rid of linker errors.&lt;/p&gt;
&lt;p&gt;As you might have noticed from the ELF contents, Arduino has two code sections,
&lt;code class="docutils literal"&gt;iram0.text&lt;/code&gt; and &lt;code class="docutils literal"&gt;flash.text&lt;/code&gt;, while I only have one, &lt;code class="docutils literal"&gt;.text&lt;/code&gt;. For that
reason, I decided to assign my &lt;code class="docutils literal"&gt;.text&lt;/code&gt; section to flash since it has much more
space. However, there was still no sign of life from the cardputer. Since I
noticed that my code was small enough to fit entirely inside the SRAM, and
Arduino's code started from SRAM, I decided to try that, and it worked!!&lt;/p&gt;
&lt;img alt="{image}/led.jpg" src="/images/cardos-without-arduino/led.jpg" /&gt;
&lt;p&gt;The ESP-IDF's &lt;a class="reference external" href="https://docs.espressif.com/projects/esp-idf/en/stable/esp32/api-guides/startup.html#second-stage-bootloader"&gt;application startup flow&lt;/a&gt; documentation describes what the
second-stage bootloader (which is the one I'm relying on from Arduino) does and
does not, and it mentions that it is the application's duty to finish setting up
the flash &lt;a class="reference external" href="https://en.wikipedia.org/wiki/Memory_management_unit"&gt;MMU&lt;/a&gt;. That is probably why using the flash address for the code didn't
work and why Arduino splits the code between an SRAM and a flash part. So at
some point I will need to set up access to flash, but for now SRAM was enough.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="initialization-code"&gt;
&lt;h2&gt;Initialization code&lt;/h2&gt;
&lt;p&gt;Now that I had at least an LED turning on, I changed the code back to full
operation, that is, initializing the display, rendering the shell and reading
the keyboard.&lt;/p&gt;
&lt;p&gt;The screen started getting cleared as usual but stopped midway. I removed the
screen clearing routine temporarily and the shell prompt was written to the
screen. I could type in, but after a short interval my position would go back to
the intial one. I realized that the Cardputer was getting reset after a precise
interval.&lt;/p&gt;
&lt;p&gt;I remembered from the &lt;a class="reference external" href="https://docs.espressif.com/projects/esp-idf/en/stable/esp32/api-guides/startup.html#second-stage-bootloader"&gt;application startup flow&lt;/a&gt; documentation that the
bootloader enabled the &lt;a class="reference external" href="https://en.wikipedia.org/wiki/Watchdog_timer"&gt;watchdog&lt;/a&gt;. And since now the application code was fully
provided by me, I had to take care of it myself: either keep feeding the
watchdog or disable it.&lt;/p&gt;
&lt;p&gt;I chose to disable the watchdog for simplicity (as always), and after
referencing the watchdog section in the &lt;a class="reference external" href="https://www.espressif.com/sites/default/files/documentation/esp32-s3_technical_reference_manual_en.pdf"&gt;ESP32-S3 manual&lt;/a&gt; and adding a few
register writes to the code, it was done: the system no longer crashed. This
allowed the shell to stay on screen but it was garbled:&lt;/p&gt;
&lt;img alt="{image}/bss.jpg" src="/images/cardos-without-arduino/bss.jpg" /&gt;
&lt;p&gt;Again, remembering from the &lt;a class="reference external" href="https://docs.espressif.com/projects/esp-idf/en/stable/esp32/api-guides/startup.html#second-stage-bootloader"&gt;application startup flow&lt;/a&gt; page, the application
code (me!) was responsible for initializing the &lt;code class="docutils literal"&gt;.bss&lt;/code&gt; section to zero. Since
the &lt;code class="docutils literal"&gt;.bss&lt;/code&gt; section contains the zero-initialized variables, if I don't
initialize it, those variables will contain trash, which is what I was seeing
here.&lt;/p&gt;
&lt;p&gt;I didn't know how to do this the proper way, but I could definitely do it the
dirty way, that is, manually listing all global variables in a function and
zeroing them out 🙈. Which I did and it worked! (Note: I have figured out how to
do it properly since and I did it in &lt;a class="reference external" href="https://codeberg.org/nfraprado/cardOS/commit/a0030f679226ba4c9d296962968675d45dae2939"&gt;this commit&lt;/a&gt;)&lt;/p&gt;
&lt;p&gt;Finally I had a working OS without needing Arduino to build! And that's the
full story behind the &amp;quot;&lt;a class="reference external" href="https://codeberg.org/nfraprado/cardOS/commit/85553f797cf0877e4c88a8e549052a445d00e7d1"&gt;Move from Arduino to generic C build flow&lt;/a&gt;&amp;quot; commit.&lt;/p&gt;
&lt;p&gt;There was still one difference from before the move though, it was much slower.
That's because, for the last time referring back to the &lt;a class="reference external" href="https://docs.espressif.com/projects/esp-idf/en/stable/esp32/api-guides/startup.html#second-stage-bootloader"&gt;application startup
flow&lt;/a&gt; page, the application is supposed to set the CPU clock frequency to the
desired value. The default frequency is quite slow, but the startup code
implicitly embedded by Arduino would set it to a higher value. I fixed that &lt;a class="reference external" href="https://codeberg.org/nfraprado/cardOS/commit/2034cf7d5223fd242e8827a169218559366e9496"&gt;in
a follow up commit&lt;/a&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="further-improvements"&gt;
&lt;h2&gt;Further improvements&lt;/h2&gt;
&lt;p&gt;With the move away from Arduino done, I was finally able to work on some much
needed improvements.&lt;/p&gt;
&lt;p&gt;The first thing I did was to split the monolithic source file into several
different files in &lt;a class="reference external" href="https://codeberg.org/nfraprado/cardOS/commit/6c8dc5baa9ed1447b1e5083109b742f6eda68f32"&gt;this commit&lt;/a&gt;. I was really looking forward to this and it
felt great to finally do it 😌. Now the code is much more organized, it's easier
to find things and to focus on a single component.&lt;/p&gt;
&lt;p&gt;I also enabled all the main compiler warnings in &lt;a class="reference external" href="https://codeberg.org/nfraprado/cardOS/commit/f2dfd26dd05748ebef2312aedb8d88aeeda607b7"&gt;this commit&lt;/a&gt;, including
warning about implicit fallthrough in case switches, which would have saved me
some minutes investigating a bug early in this project.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="conclusion"&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;This was another great step for the project. I had a lot of fun and learned so
much from it.&lt;/p&gt;
&lt;p&gt;The next big thing to tackle is implementing SDcard read and write, and a file
system. It will likely be a while before I get that done and come back with an
update on the blog. Feel free to check the &lt;a class="reference external" href="https://codeberg.org/nfraprado/cardOS"&gt;repository&lt;/a&gt; for the latest updates
in the meantime if you're curious!&lt;/p&gt;
&lt;/div&gt;
</content><category term="2024"/><category term="cardputer"/><category term="esp32"/><category term="os"/></entry><entry><title>CardOS: Writing an OS for the Cardputer</title><link href="https://nfraprado.net/post/cardos-writing-an-os-for-the-cardputer.html" rel="alternate"/><published>2024-05-25T00:00:00-03:00</published><updated>2024-05-25T00:00:00-03:00</updated><author><name>Nícolas F. R. A. Prado</name></author><id>tag:nfraprado.net,2024-05-25:/post/cardos-writing-an-os-for-the-cardputer.html</id><summary type="html">&lt;p&gt;I recently got the &lt;a class="reference external" href="https://docs.m5stack.com/en/core/Cardputer"&gt;M5Stack's Cardputer&lt;/a&gt;. I was motivated to get it because I
knew other people personally that had it too, so we would be able to share our
progress, but I was worried it might end up being just another board that sits
in my closet forever untouched …&lt;/p&gt;</summary><content type="html">&lt;p&gt;I recently got the &lt;a class="reference external" href="https://docs.m5stack.com/en/core/Cardputer"&gt;M5Stack's Cardputer&lt;/a&gt;. I was motivated to get it because I
knew other people personally that had it too, so we would be able to share our
progress, but I was worried it might end up being just another board that sits
in my closet forever untouched. Still I decided to give it a shot.&lt;/p&gt;
&lt;p&gt;Once I got it in my hands, it really won me. It has all that's required to be a
computer: full keyboard for input, display for output, battery for portability,
microSD card slot for persistent storage, plus WiFi and Bluetooth for
connectivity. The fact that it's a very limited computer, and that it's so tiny,
only makes it more charming to me.&lt;/p&gt;
&lt;p&gt;I am really interested in the idea of implementing a full system from scratch in
order to understand all the different layers and interactions. I've also always
wanted to make my own operating system (OS). So before I knew it, I had set on a
goal: I was going to make an OS from scratch for the Cardputer.&lt;/p&gt;
&lt;p&gt;Now, when I say from scratch, I don't mean necessarily starting from an empty
file, just that eventually all the code should have been written by me.
One thing I remember from &lt;a class="reference external" href="https://www.youtube.com/c/AndreasKling"&gt;Andreas Kling&lt;/a&gt;'s (creator of SerenityOS) videos is
that he didn't start writing his OS from the bootloader, but from the user
interface, and that allowed him to see his changes take effect right away and
work in small steps towards the full OS. I also remember &lt;a class="reference external" href="https://en.wikipedia.org/wiki/Tarn_Adams"&gt;Tarn Adams&lt;/a&gt;
(co-creator of Dwarf Fortress) giving the same tip about how to keep motivation
when developing a game. And both of those are people I dearly admire, so I took
their advice.&lt;/p&gt;
&lt;p&gt;So with that in mind, I started my project from one of the &lt;a class="reference external" href="https://github.com/m5stack/M5Cardputer/blob/master/examples/Basic/keyboard/inputText/inputText.ino"&gt;Cardputer's demo
code&lt;/a&gt; which already had a working display and keyboard, and &lt;a class="reference external" href="https://codeberg.org/nfraprado/cardOS/commit/8b1e75cfb9064f27d641296f06ff0fa89ac2dadc"&gt;I wrote a basic
shell on top of that&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Only after that did I start implementing my own keyboard and display support
from scratch and removing the code from the demo.&lt;/p&gt;
&lt;div class="section" id="getting-the-display-to-work"&gt;
&lt;h2&gt;Getting the display to work&lt;/h2&gt;
&lt;p&gt;Implementing display support was much trickier than I expected and got me stuck
for a while. By then I had already implemented my own keyboard support, tested
it and it worked. But I couldn't even get the display to light up, even after
carefully reading its datasheet many times and implementing the whole
initialization procedure.&lt;/p&gt;
&lt;p&gt;Eventually I took a step back and tested more basic functionality and realized I
did not have output GPIOs figured out. This was really surprising as it was
needed for the keyboard, which I had already tested. But what I hadn't realized
was that even though my code was able to toggle output pins to scan the
keyboard, it was implicitly relying on the demo's initialization code to have
working pin output, which is why when I replaced all that code with my own
display implementation neither the display nor the keyboard worked.&lt;/p&gt;
&lt;p&gt;But as often times is the case for unexplainable behaviors, that wasn't the only
issue. As it turns out, the ESP32 manual also showed the wrong address for one
of the registers needed to configure the GPIOs. Luckily it also showed the right
address in a different place, so I eventually noticed it.&lt;/p&gt;
&lt;p&gt;So after I implemented support for &lt;a class="reference external" href="https://codeberg.org/nfraprado/cardOS/commit/5dda03f7af0d61af3ad2d81191a04d38ab7cc790"&gt;enabling the output GPIOs&lt;/a&gt;, and &lt;a class="reference external" href="https://codeberg.org/nfraprado/cardOS/commit/0c380d6e9bb28816c5d55074d52bfca961696a2e"&gt;corrected
the register address&lt;/a&gt;, I was able to &lt;a class="reference external" href="https://codeberg.org/nfraprado/cardOS/commit/3095638dba5e6d40be7b592cfa52fb3835f35a83"&gt;fix the keyboard support to be
completely self-contained&lt;/a&gt;, and was finally, after a lot of sweat, able to
&lt;a class="reference external" href="https://codeberg.org/nfraprado/cardOS/commit/4706bea4bbdbc234c0054cd09c26a91a50865b5c"&gt;implement display support&lt;/a&gt; and remove the dependency from the external
library.&lt;/p&gt;
&lt;p&gt;This has been the biggest achievement on the project so far, and I'm really
proud of it!&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="current-status"&gt;
&lt;h2&gt;Current status&lt;/h2&gt;
&lt;p&gt;This is where the project is right now:&lt;/p&gt;
&lt;object data="/images/cardos/cardos_demo.mp4" type="video/mp4"&gt;{image}/cardos_demo.mp4&lt;/object&gt;
&lt;p&gt;In other words, keyboard and display support are there without relying on third
party libraries. The display communication is quite slow, so there's a lot of
room for future improvement 🙂. There's a shell with just a couple commands,
including &lt;code class="docutils literal"&gt;help&lt;/code&gt; to show the commands, &lt;code class="docutils literal"&gt;clear&lt;/code&gt; to clear the screen, &lt;code class="docutils literal"&gt;read&lt;/code&gt;
to read an arbitrary memory address and &lt;code class="docutils literal"&gt;led&lt;/code&gt; to turn the built-in RGB LED to
the desired color. It's possible to cycle through the shell's history to repeat
a previous command. And once the terminal fills the screen it automatically
clears it.&lt;/p&gt;
&lt;p&gt;I've also spent some time fixing bugs. At this point it felt like the OS was
finally in a usable state, so &lt;a class="reference external" href="https://codeberg.org/nfraprado/cardOS/releases/tag/v0.1"&gt;I've tagged it&lt;/a&gt; &lt;code class="docutils literal"&gt;v0.1&lt;/code&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="next-steps"&gt;
&lt;h2&gt;Next steps&lt;/h2&gt;
&lt;p&gt;The next thing I'm working on is to move away from the Arduino toolchain. The
main reason is that there's some code that gets implicitly added to the binary
when using it, and I want to keep removing dependencies from my code.&lt;/p&gt;
&lt;p&gt;Besides that, Arduino basically enforces the usage of a single source file. The
cardOS source is currently a single file that is 1900 lines long (with almost
600 being &lt;a class="reference external" href="https://codeberg.org/nfraprado/cardOS/src/commit/de836cdfa14bc63822665833ba27c57002b74f39/cardOS.ino#L1122"&gt;the struct for the font characters&lt;/a&gt;) and I'm starting to get lost
in it, so it's time to split the code to multiple files.&lt;/p&gt;
&lt;p&gt;Finally, Arduino uses C++, and since I'm coding in C, moving away would allow me
to use a C compiler instead, which will improve the error messages.&lt;/p&gt;
&lt;p&gt;After that I'm going to keep making cardOS more of an actual OS. I want it to
feel like a real hackable computer, so what I'm aiming at is to be able to open
a text editor, edit a shell initialization file, save, reboot and see the
effects of that change. That already gives me enough work: I need to make a text
editor, a shell script language, and implement SDcard reader and filesystem
support.&lt;/p&gt;
&lt;p&gt;Further in the future I'm really curious about implementing support for the WiFi
and maybe be able to send and receive IRC messages, if that's feasible. I'm sure
it'll be hard, but should be fun too 😃.&lt;/p&gt;
&lt;p&gt;And that's what this is all about: fun! It's been two months since I started the
project, and I can confidently say this is the project I've had the most fun
ever. So as long as it stays fun I'll keep going with it. There's no rush to get
anything done, I'm just enjoying the ride 🙂.&lt;/p&gt;
&lt;/div&gt;
</content><category term="2024"/><category term="cardputer"/><category term="esp32"/><category term="os"/></entry><entry><title>vCard + RSS as an alternative to social media</title><link href="https://nfraprado.net/post/vcard-rss-as-an-alternative-to-social-media.html" rel="alternate"/><published>2024-03-22T00:00:00-03:00</published><updated>2024-03-22T00:00:00-03:00</updated><author><name>Nícolas F. R. A. Prado</name></author><id>tag:nfraprado.net,2024-03-22:/post/vcard-rss-as-an-alternative-to-social-media.html</id><summary type="html">&lt;p&gt;Last year after talking for a while with someone during a conference they asked
me for my LinkedIn to be able to connect with me, to which I answered I didn't
have one.&lt;/p&gt;
&lt;p&gt;It was many years ago when I decided to leave social media. I don't miss it.
Instant …&lt;/p&gt;</summary><content type="html">&lt;p&gt;Last year after talking for a while with someone during a conference they asked
me for my LinkedIn to be able to connect with me, to which I answered I didn't
have one.&lt;/p&gt;
&lt;p&gt;It was many years ago when I decided to leave social media. I don't miss it.
Instant messaging allows me to keep in touch with the people that really matter
to me in a much more personal way.&lt;/p&gt;
&lt;p&gt;Still, this interaction stayed in the back of my mind. It would indeed be nice
to keep track of these connections too. The people you had an interesting
conversation with for just a few minutes. To be able to check how they're doing
every now and then, who they're working for and what they're hacking on.&lt;/p&gt;
&lt;p&gt;Essentially I would like for everyone to have an online profile and feed, which
I could easily check and subscribe to, and that this subscription data could be
stored locally. Why should that be exclusive to social media?&lt;/p&gt;
&lt;p&gt;It's not. In fact, the feed part has already been solved for quite a long time
with &lt;a class="reference external" href="https://en.wikipedia.org/wiki/RSS"&gt;RSS&lt;/a&gt;/&lt;a class="reference external" href="https://en.wikipedia.org/wiki/Atom_(web_standard)"&gt;Atom&lt;/a&gt;. So by just having the URL for someone's RSS/Atom feed, you can
add it to your feed reader and easily follow the person's updates wherever
they're published to.&lt;/p&gt;
&lt;p&gt;The profile part, a place with the key points about a person's current status
(name, photo, location, employer, etc), is the one that has historically been
tied to social media. Personal websites do many times have an &amp;quot;about&amp;quot; page with
such information, but I find the lack of structure makes it way less useful than
a social media's profile.&lt;/p&gt;
&lt;p&gt;However, there's actually a widely used open standard for encoding profiles as
well: &lt;a class="reference external" href="https://en.wikipedia.org/wiki/VCard"&gt;vCard&lt;/a&gt;. It's mainly known as the format used on email clients to store
contacts, where they're even synchronized between server and clients using the
&lt;a class="reference external" href="https://en.wikipedia.org/wiki/CardDAV"&gt;CardDAV&lt;/a&gt; protocol. But vCards are also used for contacts in your phone: it's
possible to import and export contacts on iOS and Android using vCards.&lt;/p&gt;
&lt;p&gt;So that's when I realized, if there's a widely used open standard for feeds, and
another for profiles, why not join the two? By embedding an RSS/Atom feed URL in
a vCard, it becomes a single file that has all the information needed to
identify a person and follow their updates. Since it's a single file, it's easy
to share it with others as well as store the vCards for all your connections
locally.&lt;/p&gt;
&lt;p&gt;Now one interesting bit is that though the profile information on the vCard
might get outdated, there is actually a &lt;code class="docutils literal"&gt;SOURCE&lt;/code&gt; property which is meant to
contain the URL where the latest version of the vCard can be retrieved. So by
hosting your vCard in a public URL, say your personal website, it's possible for
people that have already downloaded it to keep it up to date.&lt;/p&gt;
&lt;p&gt;As for how the feed URL can be encoded in the vCard, after skimming over
&lt;a class="reference external" href="https://datatracker.ietf.org/doc/html/rfc6350"&gt;the latest version of the spec&lt;/a&gt;, I originally intended to have it as a plain
&lt;code class="docutils literal"&gt;URL&lt;/code&gt; property. But I wanted the main URL in the vCard to still be the
person's website, so some mechanism would be needed to differentiate between the
two URLs. At first I thought of using a &lt;code class="docutils literal"&gt;MEDIATYPE&lt;/code&gt; parameter in the &lt;code class="docutils literal"&gt;URL&lt;/code&gt;,
but the spec explicitly said that for protocols such as HTTP the
&lt;code class="docutils literal"&gt;Content-type&lt;/code&gt; HTML header should be used instead. However, as it seems,
despite RSS/Atom feeds having standardized media types (&lt;code class="docutils literal"&gt;application/rss+xml&lt;/code&gt;
and &lt;code class="docutils literal"&gt;application/atom+xml&lt;/code&gt; respectively), in practice, &lt;code class="docutils literal"&gt;text/xml&lt;/code&gt; is used
instead. In the end, this means that in order to tell that an URL in the vCard
is for an RSS/Atom feed, the URL would need to be fetched and read. To avoid
that complication, I decided, reluctantly, to use a non-standard property
instead, &lt;code class="docutils literal"&gt;X-FEED&lt;/code&gt;, for the feed.&lt;/p&gt;
&lt;div class="section" id="trying-it-out"&gt;
&lt;h2&gt;Trying it out&lt;/h2&gt;
&lt;p&gt;In order to try the idea out, I created &lt;a class="reference external" href="https://codeberg.org/nfraprado/vcard-network"&gt;this repository&lt;/a&gt; with a couple simple
scripts. First there's a &lt;code class="docutils literal"&gt;vcard-render&lt;/code&gt; script that parses vCards and renders
them using a provided &lt;a class="reference external" href="https://en.wikipedia.org/wiki/Jinja_(template_engine)"&gt;jinja&lt;/a&gt; template. The idea is that you can use it to render
your vCard and your connections' vCards in your website for example, which is
exactly what I use it for &lt;a class="reference external" href="https://nfraprado.net/pages/vcard-network.html"&gt;in this page I've added to the website&lt;/a&gt; (it can be
found from the About page). If you're curious as to how I integrated the script
into my website, see &lt;a class="reference external" href="https://codeberg.org/nfraprado/blog/commit/148c9cf31eb10434ba127e94332e51397f18db99"&gt;this commit&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The other script is &lt;code class="docutils literal"&gt;vcard-to-opml&lt;/code&gt; which extracts the RSS feed from the
vCards supplied and generates an OPML file with them, which can then be imported
into a feed reader. The idea here is to run this script on the vCards of all the
people you have downloaded (your connections) so you can easily follow their
feeds. I use &lt;a class="reference external" href="https://newsboat.org/"&gt;newsboat&lt;/a&gt; as my feed reader, so I would add the following command
to a crontab to run it periodically (as soon as I get some real connections!):&lt;/p&gt;
&lt;pre class="code literal-block"&gt;
newsboat -i &amp;lt;(awk 1 ~/ark/etc/dav/contacts/5DEB-65D21680-2F1-4AEA3480/* | ./vcard-to-opml.py)
&lt;/pre&gt;
&lt;p&gt;Note: &lt;code class="docutils literal"&gt;awk&lt;/code&gt; is used here instead of a simple &lt;code class="docutils literal"&gt;cat&lt;/code&gt; because apparently those
vCards don't have a newline at the end of the file. I'm not sure if it's my
email provider's fault, where I store my vCards, or &lt;a class="reference external" href="http://vdirsyncer.pimutils.org/en/stable/"&gt;vdirsyncer&lt;/a&gt;'s fault, which
is the program I use to have a local copy of my vCards.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="conclusion"&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;If you're interested in trying out using vCards and RSS to connect to people as
well, check out the &lt;a class="reference external" href="https://codeberg.org/nfraprado/vcard-network"&gt;repository&lt;/a&gt; that contains those scripts. And feel free to
reach out if you do so, you can find my email on... you guessed it, my vCard 😉.&lt;/p&gt;
&lt;p&gt;Even if no one ever uses this, at least I now have a proper answer for the next
person that asks for my LinkedIn. &amp;quot;Just scan this QR code and you'll see my
profile&amp;quot; 🙂.&lt;/p&gt;
&lt;img alt="{image}/qrcode.png" src="/images/vcard-network/qrcode.png" /&gt;
&lt;/div&gt;
</content><category term="2024"/><category term="vcard"/><category term="rss"/><category term="atom"/><category term="social media"/></entry><entry><title>Collecting pins</title><link href="https://nfraprado.net/post/collecting-pins.html" rel="alternate"/><published>2023-11-23T00:00:00-03:00</published><updated>2023-11-23T00:00:00-03:00</updated><author><name>Nícolas F. R. A. Prado</name></author><id>tag:nfraprado.net,2023-11-23:/post/collecting-pins.html</id><summary type="html">&lt;p&gt;It started innocently about one year ago as I was looking for a gift for a
friend. We played a lot of League of Legends and Ragnarok Online back in the
day, so I wanted to give him something to remember that. Eventually I came
across these pins:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;a class="reference external" href="https://www.etsy.com/listing/587004849/poring-poporing-slime-monter-fanart-hard"&gt;Poring/Poporing …&lt;/a&gt;&lt;/li&gt;&lt;/ul&gt;</summary><content type="html">&lt;p&gt;It started innocently about one year ago as I was looking for a gift for a
friend. We played a lot of League of Legends and Ragnarok Online back in the
day, so I wanted to give him something to remember that. Eventually I came
across these pins:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;a class="reference external" href="https://www.etsy.com/listing/587004849/poring-poporing-slime-monter-fanart-hard"&gt;Poring/Poporing from Ragnarok Online&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference external" href="https://www.etsy.com/listing/1280961309/arcane-enamel-pin-fanmade-vis-hextech"&gt;Vi's Gauntlet from League of Legends&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;They were exactly what I was looking for so I bought them. That was going to be
it, but I am still personally a huge fan of Ragnarok Online, and those pins were
so cute that I thought, I should buy them for me too!&lt;/p&gt;
&lt;p&gt;But after that I couldn't stop looking for more pins. I was hooked! I kept
browsing Etsy, searching for pins from games I like, and favoriting them so I
could find them later.&lt;/p&gt;
&lt;p&gt;I also started looking for pins at every merchandise store I came by, which I'd
never given much attention previously. I now actually had something of interest
to buy! So after a few weeks I got a small collection:&lt;/p&gt;
&lt;img alt="{image}/pins-before-pax.jpg" src="/images/pins/pins-before-pax.jpg" /&gt;
&lt;p&gt;Around that time I was thinking about attending &lt;a class="reference external" href="https://en.wikipedia.org/wiki/PAX_(event)"&gt;PAX east&lt;/a&gt;. When I found out the
event attracted a big community of pin collectors and hosted pin trading events,
from &lt;a class="reference external" href="https://www.youtube.com/watch?v=7hoSSI6A-_U"&gt;this video&lt;/a&gt;, I knew I had to go.&lt;/p&gt;
&lt;p&gt;In order to be able to conveniently bring my pins to PAX, and to bring back the
ones I would get there, I bought a &lt;a class="reference external" href="https://www.amazon.com/dp/B07YYR3J1V/ref=cm_sw_r_as_gl_api_gl_i_NJTTQQ2YXTGC585NB7YN"&gt;PinFolio Classic&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;On my first day at PAX I didn't hold back and bought every nice pin I saw:&lt;/p&gt;
&lt;img alt="{image}/pins-bought-1st-day-pax.jpg" src="/images/pins/pins-bought-1st-day-pax.jpg" /&gt;
&lt;p&gt;After lucking out and winning two golden cassette pins in a row from a carnival
wheel that was setup there, I went to the pin trading room to trade some pins
and meet new people. It was fun to recognize some of the same people that showed
up on the video.&lt;/p&gt;
&lt;p&gt;With PAX over, this was my collection:&lt;/p&gt;
&lt;img alt="{image}/pinfolio-after-pax.jpg" src="/images/pins/pinfolio-after-pax.jpg" /&gt;
&lt;p&gt;(the Deviruchi pin is missing since it was pinned to my beanie)&lt;/p&gt;
&lt;p&gt;Now, many months later I have finally filled up a display case with my
collection. Note that I have some more pins, but I plan to use those for
trading in future events, so they're stored in the pinfolio. So without further
ado, here is my collection, labeled:&lt;/p&gt;
&lt;img alt="{image}/pins-full-labeled.jpg" src="/images/pins/pins-full-labeled.jpg" /&gt;
&lt;ol class="arabic simple"&gt;
&lt;li&gt;Poring. From Ragnarok Online. Fan-made (Teaberryhouse). The one that started it all! &lt;a class="reference external" href="https://www.etsy.com/listing/587004849/poring-poporing-slime-monter-fanart-hard"&gt;Link&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Poporing. See 1.&lt;/li&gt;
&lt;li&gt;&lt;code class="docutils literal"&gt;/gg&lt;/code&gt; Emote. Came with 4.&lt;/li&gt;
&lt;li&gt;Deviruchi. From Ragnarok Online. Fan-made (FinniChang). Bought together with 1 and 2. &lt;a class="reference external" href="https://www.etsy.com/listing/742804941/deviruchi-gg-ragnarok-online-enamel-pin"&gt;Link&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Eevee. From Pokemon. Fan-made (Jackalandhare). I was buying two pins for a friend that loves
Pokemon and the third was cheaper, so I got one for myself as well. &lt;a class="reference external" href="https://www.etsy.com/listing/729815460/hard-enamel-pins-eeveelution-eevee?variation0=1222773687"&gt;Link&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Fairy bottle. From Zelda. Fan-made (FinniChang). &lt;a class="reference external" href="https://www.etsy.com/listing/842366471/fairy-bottle-enamel-pin?variation0=1473891219"&gt;Link&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Van Gogh's The Starry Night. Got it while visiting the Museum of Modern Art
in NYC, where they keep the real painting.&lt;/li&gt;
&lt;li&gt;Prince &amp;amp; Katamari. From Katamari Damacy. I played Katamari Damacy Reroll last
year and thought it was really cool! And who can resist a spinning pin?
Bought at the Fangamer stand at PAX. &lt;a class="reference external" href="https://www.fangamer.com/collections/katamari-damacy/products/katamari-damacy-the-prince-katamari-spinning-pin"&gt;Link&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;King. See 8. &lt;a class="reference external" href="https://www.fangamer.com/collections/katamari-damacy/products/king-of-all-cosmos-pin-spinning"&gt;Link&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Ravenclaw. From Harry Potter. I bought it at a Harry Potter store in the
London airport, where I had a connection. I don't really care about
Ravenclaw, but it was the best-looking pin there.&lt;/li&gt;
&lt;li&gt;Junimos. From Stardew Valley. Bought at the Fangamer stand at PAX. &lt;a class="reference external" href="https://www.fangamer.com/collections/stardew-valley/products/pinverse-junimos-pin-pack"&gt;Link&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Came with 11.&lt;/li&gt;
&lt;li&gt;Babaa (Apparently that's its name!). From Neopets. I played a lot of Neopets
when I was younger, so when I saw these available at a Geekify stand at PAX,
I just had to buy some.&lt;/li&gt;
&lt;li&gt;Strawberry &amp;amp; Chocolate Paintbrush. See 13.&lt;/li&gt;
&lt;li&gt;Wraith Paintbrush. See 13.&lt;/li&gt;
&lt;li&gt;Fight Button. From Undertale. Big Undertale fan, as will be obvious from
this list, so I just went crazy. Bought at Fangamer stand at PAX. &lt;a class="reference external" href="https://www.fangamer.com/collections/undertale/products/undertale-mercy-or-fight-enamel-pin-set"&gt;Link&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Mercy Button. Came with 16.&lt;/li&gt;
&lt;li&gt;Annoying dog. See 16. &lt;a class="reference external" href="https://www.fangamer.com/collections/undertale/products/undertale-annoying-dog-lapel-pin"&gt;Link&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Wonderville Arcade machine. Bought while visiting &lt;a class="reference external" href="https://www.wonderville.nyc/"&gt;Wonderville&lt;/a&gt; with a
friend, which is really a unique and awesome place!&lt;/li&gt;
&lt;li&gt;Psychokinetic Raz. From Psychonauts 2. I haven't played the game yet, just
the first one, but I had just finished watching the &lt;a class="reference external" href="https://www.youtube.com/watch?v=kRlI72bsNRc&amp;amp;list=PLIhLvue17Sd70y34zh2erWWpMyOnh4UN_"&gt;PsychOdyssey
documentary&lt;/a&gt; and was blown away so I had to buy something to remember it.
Bought at Fangamer stand at PAX. &lt;a class="reference external" href="https://www.fangamer.com/products/raz-psychonauts-pin-spinning"&gt;Link&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Phantom of the Opera. Bought it on the way out after watching the play on
Broadway. It was great!&lt;/li&gt;
&lt;li&gt;Canada Wilderness. Gifted from a friend. He bought it during our trip to
Toronto (at the store on the base of the tower, I believe)&lt;/li&gt;
&lt;li&gt;Loto. Gifted from a friend. It's an old one got from the second hand market,
unlike all the others in this collection. I love how it has actual numbers
for each tiny square.&lt;/li&gt;
&lt;li&gt;Frisk. From Undertale. Bought at Fangamer stand at PAX. &lt;a class="reference external" href="https://www.fangamer.com/collections/undertale/products/undertale-character-pins-set-1"&gt;Link&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Toriel. Came with 24.&lt;/li&gt;
&lt;li&gt;Sans. Came with 24.&lt;/li&gt;
&lt;li&gt;Flowey. Came with 24.&lt;/li&gt;
&lt;li&gt;Papyrus. See 24. &lt;a class="reference external" href="https://www.fangamer.com/collections/undertale/products/undertale-character-pins-set-2"&gt;Link&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Undyne. Came with 28.&lt;/li&gt;
&lt;li&gt;Alphys. Came with 28.&lt;/li&gt;
&lt;li&gt;Mettaton. Came with 28.&lt;/li&gt;
&lt;li&gt;Mask. From Mr Robot. Bought it second-hand online. It's from a 2016 Loot
Crate. I really wish it was smaller.&lt;/li&gt;
&lt;li&gt;Software Freedom Conservancy's Logo. Gifted from a friend.&lt;/li&gt;
&lt;li&gt;L's Logo. From Death Note. Bought at a geek store in Antwerp.&lt;/li&gt;
&lt;li&gt;Dwarf. From Dwarf Fortress. One of the reasons I went to PAX was to meet the
creators of the game in person (and I did!). Shortly after a Dwarf Fortress
pin was announced and I was sure to buy one online. &lt;a class="reference external" href="https://www.fangamer.com/products/dwarf-fortress-pin-finely-crafted"&gt;Link&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;PAX East VHS. Bought at PAX. People at PAX will mostly only trade Pinny
Arcade pins, so I bought this as a part of a 4-pin set to kick start my
collection and be able to trade at the event.&lt;/li&gt;
&lt;li&gt;East 2023 Logo. Came with 36.&lt;/li&gt;
&lt;li&gt;Diamond Ore Block. From Minecraft. Got this from trading at PAX. Minecraft
was one of the most influential games in my life, so I was super happy!&lt;/li&gt;
&lt;li&gt;ATLAS and P-Body. From Portal 2. Got this from trading at PAX. I love
Portal, but this is my least favorite pin out of &lt;a class="reference external" href="https://pinnydb.com/pinDetail/271"&gt;the set&lt;/a&gt;, so I hope to trade it for one of
others next PAX.&lt;/li&gt;
&lt;li&gt;Raz. From Psychonauts. I was given this one for free by someone at the
trading room at PAX. She said she already had too many pins and since I was
just starting out and liked this one, she was kind enough to give it to me
🙂.&lt;/li&gt;
&lt;li&gt;? Block. From Super Mario Bros. Bought at PAX as part of a 4-pin set to
start my Pinny Arcade collection and be able to trade. &lt;a class="reference external" href="https://store.penny-arcade.com/collections/pins/products/super-mario-bros-1-1-pin-set"&gt;Link&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Mario. Came with 41.&lt;/li&gt;
&lt;li&gt;Fairly OddParents. I watched tons of hours of this cartoon, so was really
happy to find a pin for it. To my knowledge the only official pin. Made by
Zen Monkey Studios. &lt;a class="reference external" href="https://www.ebay.com/itm/234773032756"&gt;Link&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Paranaguá train. Gifted from a friend, who took that train. I really need to
go there one day wearing this pin 😃.&lt;/li&gt;
&lt;li&gt;Kerbal Space Program. Gifted from the same person as 40.&lt;/li&gt;
&lt;li&gt;Green Flame. From Acquisitions Inc. It's a tabletop RPG campaign played live
during PAX. I watched it during PAX and got this pin to remember the moment.
&lt;a class="reference external" href="https://store.penny-arcade.com/collections/pins/products/green-flame-pin"&gt;Link&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Mae. From Night in the Woods. I bought it at PAX before even playing the
game. I just &lt;em&gt;really&lt;/em&gt; loved the style. Possibly my favorite pin.&lt;/li&gt;
&lt;li&gt;Los Andes. Gifted from a friend who visited that place, and he's really into
rock climbing so it's very fitting.&lt;/li&gt;
&lt;li&gt;Goose. From Kickstarter. During PAX the Kickstarter folks had geese
scattered across the stands of each project funded by kickstarter. If you
found all of them you would get a pin. It was incredibly hard but I did it.&lt;/li&gt;
&lt;li&gt;PAX Gold Mixtape. During PAX there was a carnival wheel that you could pay
Pinny Arcade pins to spin and get one of the mixtape pins. I was extremely
lucky and managed to get two of the golden ones. I traded one and kept this
one.&lt;/li&gt;
&lt;li&gt;Portuguese tile. Bought during a trip to Portugal.&lt;/li&gt;
&lt;li&gt;Collabora's logo. Got it during the company meetup. Unfortunately it's
magnetic, so I'm too scared to wear it on the streets and lose it.&lt;/li&gt;
&lt;li&gt;Ecuador. Gifted from a friend who visited that place, the same rock climber
as 48, see a pattern? 😝&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;So far I've been keeping my pins in a display case on a side table. I've
thought about having them on a board on the wall to make them more visible but
I'm worried they might get damaged over time being constantly exposed like
that.&lt;/p&gt;
&lt;p&gt;However I don't like to just leave my pins locked up in a case either, since
that seems like a waste, so I wear them. I've found what works best for me is
to wear them on a beanie. (See first photo) As a result I can't wear them
during Summer (I'm still looking for an alternative here). I always keep it to
a single pin, since more than that looks too noisy to me, and switch it quite
frequently and when possible to match the occasion.&lt;/p&gt;
&lt;p&gt;Now that this display case is full I'll need to buy a new one (or maybe I'll
try a board this time). I'll probably make another post when that one gets full
too, though that might take a while. I do have many pins on my wishlist, but
I'm trying not to go too crazy with it. It's about the quality not the quantity
after all, and I don't want it to get to a point where I can't appreciate every
single one.&lt;/p&gt;
</content><category term="2023"/><category term="pin"/></entry><entry><title>Meeting the Linux community at my first ELC</title><link href="https://nfraprado.net/post/meeting-the-linux-community-at-my-first-elc.html" rel="alternate"/><published>2022-09-29T00:00:00-03:00</published><updated>2022-09-29T00:00:00-03:00</updated><author><name>Nícolas F. R. A. Prado</name></author><id>tag:nfraprado.net,2022-09-29:/post/meeting-the-linux-community-at-my-first-elc.html</id><summary type="html">&lt;p&gt;This month I attended the Embedded Linux Conference Europe, which was co-located
with the Open Source Summit Europe, and took place in Dublin, Ireland. This was
my first time attending a Linux conference in-person as a contributor and it was
a very special experience.&lt;/p&gt;
&lt;p&gt;This wasn't my first ever in-person …&lt;/p&gt;</summary><content type="html">&lt;p&gt;This month I attended the Embedded Linux Conference Europe, which was co-located
with the Open Source Summit Europe, and took place in Dublin, Ireland. This was
my first time attending a Linux conference in-person as a contributor and it was
a very special experience.&lt;/p&gt;
&lt;p&gt;This wasn't my first ever in-person Linux conference though. Back in 2018 I
attended linuxdev-br. It took place right at Unicamp, where I was an
undergraduate student. I remember watching Sergio Prado's presentation on how to
write an LED driver and being fascinated with the fact that a physical LED could
be turned on and off by simply writing to a file.&lt;/p&gt;
&lt;p&gt;That conference was actually a major turning point in my life, as it was there
that I first met Helen Koike, who invited me to attend the weekly meetings for
&lt;a class="reference external" href="https://lkcamp.dev/"&gt;LKCAMP&lt;/a&gt;, where they were teaching about the kernel inner workings and guiding
people to submit their first patches and become contributors. And after learning
a lot from them and having some contributions merged I eventually made my way
into Collabora.&lt;/p&gt;
&lt;p&gt;Fast-forward to 2022 and here I was in Dublin, for my second ever in-person
Linux conference, this time at a much bigger one, and having worked for
more than a year at Collabora, as an active contributor to the Linux kernel.&lt;/p&gt;
&lt;p&gt;As a contributor, I'm used to interacting with the other kernel developers
through the mailing lists on a daily basis. And while that works efficiently for
the project, dealing with just text replies and email aliases can feel very
distant.&lt;/p&gt;
&lt;p&gt;And that's why meeting these people in real life was so reinvigorating. It's a
very nice change of pace to be able to talk to them in a more expressive and
human manner, not just about work but life in general. And to actually see with
my own eyes that these are all still real people, with their own life,
aspirations, food preferences. It reminded me that the whole world really is
built by people just like me.&lt;/p&gt;
&lt;p&gt;It was also my first time meeting with my colleagues at Collabora, which was
absolutely amazing! Picture &lt;a class="reference external" href="https://twitter.com/Collabora/status/1570761841089073153"&gt;here&lt;/a&gt;. That's me all the way on the left 🙂.&lt;/p&gt;
&lt;p&gt;There's a photo album of the event from the Linux Foundation &lt;a class="reference external" href="https://www.flickr.com/photos/linuxfoundation/albums/72177720301869820"&gt;here&lt;/a&gt; in case you
want to check it out.&lt;/p&gt;
&lt;p&gt;I barely got back from this trip and I already can't wait for the next
opportunity to meet everyone again!&lt;/p&gt;
&lt;img alt="{image}/convention-center-night.jpg" src="/images/elce/convention-center-night.jpg" /&gt;
&lt;p&gt;(The Convention Centre Dublin glowing bright at night, on the other
side of the Liffey river)&lt;/p&gt;
</content><category term="2022"/><category term="conference"/></entry><entry><title>Moving the blog to Codeberg</title><link href="https://nfraprado.net/post/moving-the-blog-to-codeberg.html" rel="alternate"/><published>2022-08-29T00:00:00-03:00</published><updated>2022-08-29T00:00:00-03:00</updated><author><name>Nícolas F. R. A. Prado</name></author><id>tag:nfraprado.net,2022-08-29:/post/moving-the-blog-to-codeberg.html</id><summary type="html">&lt;p&gt;As someone who cares about FOSS, I'm always happy to move to a FOSS alternative
when one shows up, provided there aren't any big drawbacks.&lt;/p&gt;
&lt;p&gt;Back when I was in University and starting to learn the ways of Git, I only knew
about two Git hosting options: GitHub, the mainstream …&lt;/p&gt;</summary><content type="html">&lt;p&gt;As someone who cares about FOSS, I'm always happy to move to a FOSS alternative
when one shows up, provided there aren't any big drawbacks.&lt;/p&gt;
&lt;p&gt;Back when I was in University and starting to learn the ways of Git, I only knew
about two Git hosting options: GitHub, the mainstream but proprietary, and
GitLab, the less known but more open alternative. Between the two, GitLab was
the obvious choice for my personal repositories, including this blog.&lt;/p&gt;
&lt;p&gt;A few months ago I learned about &lt;a class="reference external" href="https://codeberg.org/"&gt;Codeberg&lt;/a&gt;. Codeberg provides a hosted instance
of &lt;a class="reference external" href="https://gitea.io/"&gt;Gitea&lt;/a&gt;, which is a Git forge that is entirely FOSS. On top of that, Codeberg
is backed by a non-profit, which makes it clear that it is community-focused. As
far as I'm concerned, it doesn't get better than this, so I was eager to move
all my repositories to Codeberg.&lt;/p&gt;
&lt;p&gt;Most of my repositories are really just archives: I'm the only one pushing code
to them, and as long as the commit history is available, there's no other
feature they require. So the migration was pretty straight-forward. The only
exception is the blog repository.&lt;/p&gt;
&lt;div class="section" id="codeberg-blog-setup"&gt;
&lt;h2&gt;Codeberg blog setup&lt;/h2&gt;
&lt;p&gt;The one big missing feature in Codeberg which I relied on in GitLab for my blog
is the CI.&lt;/p&gt;
&lt;p&gt;My current setup for the blog in GitLab was to have a repository with the CI
configured so that whenever I pushed new changes, the CI would run pelican to
generate the static pages for the website and serve them.&lt;/p&gt;
&lt;p&gt;On Codeberg, since there's no CI, whatever files are present in the special
&lt;code class="docutils literal"&gt;pages&lt;/code&gt; repository are directly served. This approach required me to set up
two repositories: one for the source files, the &lt;a class="reference external" href="https://codeberg.org/nfraprado/blog"&gt;blog repository&lt;/a&gt;, and another
for the generated output to be served, the &lt;a class="reference external" href="https://codeberg.org/nfraprado/pages"&gt;pages repository&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Note&lt;/strong&gt;: &lt;a class="reference external" href="https://codeberg.page/"&gt;It's also possible&lt;/a&gt; to use a &lt;code class="docutils literal"&gt;pages&lt;/code&gt; branch in the same repository to
serve the files, but the separate repository approach seemed neater to me.&lt;/p&gt;
&lt;p&gt;But of course I didn't want to manage the extra &lt;code class="docutils literal"&gt;pages&lt;/code&gt; repository, since that
would be annoying and quickly make me miss GitLab's CI. So I added a new &lt;code class="docutils literal"&gt;make
deploy&lt;/code&gt; target in the Makefile to automate the process of deploying the site to
the &lt;code class="docutils literal"&gt;pages&lt;/code&gt; repo. It can be seen in &lt;a class="reference external" href="https://codeberg.org/nfraprado/blog/commit/44bb31065898a942164a8a23c526d9363c750d93"&gt;this commit&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;What &lt;code class="docutils literal"&gt;make deploy&lt;/code&gt; does is:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;Do the steps previously done in the &lt;a class="reference external" href="https://codeberg.org/nfraprado/blog/src/commit/38cc4cfa0b5bb34eb5d088210abbce5cbf553ff7/.gitlab-ci.yml"&gt;.gitlab-ci.yml&lt;/a&gt; to generate the blog
output, namely:&lt;ul&gt;
&lt;li&gt;Generate the translation strings (through &lt;code class="docutils literal"&gt;make trans_deploy&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Generate the static site pages (through &lt;code class="docutils literal"&gt;make publish&lt;/code&gt;)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Commit the output files and push them to the &lt;code class="docutils literal"&gt;pages&lt;/code&gt; repository&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Since the output files generated from pelican need to be commited and pushed to
the &lt;code class="docutils literal"&gt;pages&lt;/code&gt; repo, the local output directory inside the blog directory now
needs to be a git repository. To acommodate for that, the &lt;code class="docutils literal"&gt;OUTPUT_RETENTION&lt;/code&gt;
variable &lt;a class="reference external" href="https://codeberg.org/nfraprado/blog/commit/44bb31065898a942164a8a23c526d9363c750d93"&gt;needed updating&lt;/a&gt; so that pelican doesn't
delete the &lt;code class="docutils literal"&gt;.git&lt;/code&gt; folder every time it runs.&lt;/p&gt;
&lt;p&gt;I subsequently improved the commit messages for the &lt;code class="docutils literal"&gt;pages&lt;/code&gt; repo through
&lt;a class="reference external" href="https://codeberg.org/nfraprado/blog/commit/38cc4cfa0b5bb34eb5d088210abbce5cbf553ff7"&gt;this commit&lt;/a&gt;, by formatting them with both the commit hash and description,
in &lt;a class="reference external" href="https://www.kernel.org/doc/html/latest/process/submitting-patches.html#describe-your-changes"&gt;the same format&lt;/a&gt; that is used for &lt;code class="docutils literal"&gt;Fixes:&lt;/code&gt; tags in the Linux kernel.
This allows me not only to correlate any commit in the &lt;code class="docutils literal"&gt;pages&lt;/code&gt; repo to the
corresponding commit in the &lt;code class="docutils literal"&gt;blog&lt;/code&gt; repo, but also easily tell from a glance
what the latest changes were about.&lt;/p&gt;
&lt;p&gt;&lt;a class="reference external" href="https://codeberg.org/nfraprado/blog/commit/19f8d66f86038744362b6c9d23a33607e0b8ac84"&gt;I also added&lt;/a&gt; a &lt;code class="docutils literal"&gt;.domains&lt;/code&gt; file to the retention since it is required by
Codeberg for using a custom domain.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="conclusion"&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;At first the need to version output files with Git put me off, but the fact that
it's in a separate repository and it's easy to correlate the commits to the
source ones made it a non-issue for me.&lt;/p&gt;
&lt;p&gt;I did notice a couple advantages on the &lt;code class="docutils literal"&gt;pages&lt;/code&gt; repo approach over CI. First,
I don't need to think about the image and package versions I'm using for the CI
because there's no CI. As long as my local system is able to generate the blog,
I'm good (and I already needed to have a working local setup to test changes
before pushing anyway). Second, as soon as I &lt;code class="docutils literal"&gt;make deploy&lt;/code&gt;, the new changes
are live, while GitLab's CI would have taken a minute or so to update.&lt;/p&gt;
&lt;p&gt;So it took some little changes to have a good blog setup on Codeberg but I'm
happy with the move. Codeberg feels like a nice new home and I'll be sure to
take good care of it 🙂.&lt;/p&gt;
&lt;/div&gt;
</content><category term="2022"/><category term="blog"/><category term="codeberg"/></entry><entry><title>Using emojis in matplotlib</title><link href="https://nfraprado.net/post/using-emojis-in-matplotlib.html" rel="alternate"/><published>2022-07-20T00:00:00-03:00</published><updated>2022-07-20T00:00:00-03:00</updated><author><name>Nícolas F. R. A. Prado</name></author><id>tag:nfraprado.net,2022-07-20:/post/using-emojis-in-matplotlib.html</id><summary type="html">&lt;p&gt;Last month, as I was writing the blog post with all the statistics for the
blog's two year anniversary, the &lt;a class="reference external" href="/post/blog-statistics-after-two-years.html"&gt;"Blog statistics after two years" post&lt;/a&gt;, I decided that
I really wanted to have a plot with emojis. From the moment I thought of this I
knew it couldn't simply …&lt;/p&gt;</summary><content type="html">&lt;p&gt;Last month, as I was writing the blog post with all the statistics for the
blog's two year anniversary, the &lt;a class="reference external" href="/post/blog-statistics-after-two-years.html"&gt;"Blog statistics after two years" post&lt;/a&gt;, I decided that
I really wanted to have a plot with emojis. From the moment I thought of this I
knew it couldn't simply work and I was in for some fun.&lt;/p&gt;
&lt;p&gt;When I first tried generating my plot with emojis, this is how it came out:&lt;/p&gt;
&lt;img alt="{image}/emojis-traced.png" src="/images/matplotlib-emojis/emojis-traced.png" /&gt;
&lt;p&gt;These emojis just have the outline and some are even missing. I wanted
matplotlib to use a proper emoji font, like NotoColorEmoji or twemoji, which are
colorful and look nice. I had both installed on my system but they weren't being
automatically picked up by matplotlib.&lt;/p&gt;
&lt;p&gt;After a bit of searching I figured out how to explicitly add the font to
matplotlib:&lt;/p&gt;
&lt;pre class="code python literal-block"&gt;
&lt;span class="n"&gt;matplotlib&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;font_manager&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;fontManager&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;addfont&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;/usr/share/fonts/noto/NotoColorEmoji.ttf&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;
&lt;p&gt;And I also added &lt;code class="docutils literal"&gt;fontname=&amp;quot;noto color emoji&amp;quot;&lt;/code&gt; to the matplotlib call that
should draw using this font (in my case &lt;code class="docutils literal"&gt;xticks()&lt;/code&gt;).&lt;/p&gt;
&lt;p&gt;By forcing matplotlib to use the emoji font I wanted, I no longer got those
outlined emojis, in fact I didn't get any image at all 😝, just this traceback:&lt;/p&gt;
&lt;pre class="code literal-block"&gt;
 Traceback (most recent call last):
   File &amp;quot;/home/nfraprado/emoji.py&amp;quot;, line 3, in &amp;lt;module&amp;gt;
     matplotlib.font_manager.fontManager.addfont('/usr/share/fonts/noto/NotoColorEmoji.ttf')
   File &amp;quot;/usr/lib/python3.10/site-packages/matplotlib/font_manager.py&amp;quot;, line 1092, in addfont
     font = ft2font.FT2Font(path)
 RuntimeError: In FT2Font: Could not set the fontsize (error code 0x17)
&lt;/pre&gt;
&lt;p&gt;With an error code even, inviting me to dig deeper 😆. Since I'm not familiar
with the inner workings of fonts, the first thing was to figure out what FT2Font
was. And &lt;a class="reference external" href="https://github.com/matplotlib/matplotlib/blob/main/src/ft2font.cpp"&gt;it turned out to be&lt;/a&gt; matplotlib's wrapper for &lt;a class="reference external" href="https://freetype.org/"&gt;FreeType&lt;/a&gt;, the library
that renders the fonts.&lt;/p&gt;
&lt;p&gt;The traceback error message was coming from &lt;a class="reference external" href="https://github.com/matplotlib/matplotlib/blob/60ae76b6b5b071e45d1ab652464f68a592cf977d/src/ft2font.cpp#L354"&gt;this line&lt;/a&gt;, which meant that
&lt;code class="docutils literal"&gt;FT_Set_Char_Size()&lt;/code&gt;, which is a freetype function, was failing with error
code &lt;code class="docutils literal"&gt;0x17&lt;/code&gt;. A look into &lt;a class="reference external" href="https://freetype.org/freetype2/docs/reference/ft2-error_code_values.html"&gt;freetype's error code reference&lt;/a&gt; revealed that it
meant &amp;quot;invalid pixel size&amp;quot;.&lt;/p&gt;
&lt;p&gt;As I tried to figure out what differed the emoji fonts from normal fonts, I did
notice something that seemed to be fundamental: Running &lt;code class="docutils literal"&gt;fc-scan&lt;/code&gt; on the emoji
fonts revealed that they had a &lt;code class="docutils literal"&gt;pixelsize&lt;/code&gt; property which wasn't present in
normal fonts. Furthermore, running &lt;code class="docutils literal"&gt;ftview&lt;/code&gt; (from the &lt;code class="docutils literal"&gt;freetype2-demos&lt;/code&gt;
package) on the emoji fonts with any size resulted in the emojis being drawn in
the same size, while normal fonts were correctly scaled.&lt;/p&gt;
&lt;p&gt;At this point I needed to actually dig into the code, so I cloned the source for
matplotlib and freetype, compiled, and set the environment so I could use them:
installed matplotlib in a virtual environment with &lt;code class="docutils literal"&gt;pip install -e .&lt;/code&gt; and
pointed the &lt;code class="docutils literal"&gt;LD_LIBRARY_PATH&lt;/code&gt; environment variable to the directory containing
the build output from freetype.&lt;/p&gt;
&lt;p&gt;After enabling debug logs in freetype (with &lt;code class="docutils literal"&gt;FT2_DEBUG=any:5&lt;/code&gt;) and adding some
logs of my own, I noticed that the difference in code run for the emoji fonts
was due to &lt;code class="docutils literal"&gt;FT_HAS_FIXED_SIZES&lt;/code&gt; being true, whose meaning can be seen &lt;a class="reference external" href="https://gitlab.freedesktop.org/freetype/freetype/-/blob/e7482ff4c2a39e0e6bcf32b90ccfbfdd0f8ef5e6/include/freetype/freetype.h#L1372"&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;To sum up the issue here (to my understanding): emoji fonts are made by
embedding bitmaps, which have a specific size. This is indicated by
&lt;code class="docutils literal"&gt;FT_HAS_FIXED_SIZES&lt;/code&gt; being true, and the size of these bitmaps shows up as the
&lt;code class="docutils literal"&gt;pixelsize&lt;/code&gt; attribute. When matplotlib is going to draw the font, it specifies
the size it wants to render the font in, but since the emoji font has a fixed
size, the freetype code expects the size passed in to be the same as the font
size. This is because you can have multiple versions of the bitmaps, with
different sizes, inside the same font, so freetype would pick the one with the
size that was asked for.&lt;/p&gt;
&lt;p&gt;With that in mind, I came up with this patch:&lt;/p&gt;
&lt;pre class="code diff literal-block"&gt;
&lt;span class="gh"&gt;diff --git a/src/base/ftobjs.c b/src/base/ftobjs.c&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="gh"&gt;index f66273f3d3cb..a59a61119702 100644&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="gd"&gt;--- a/src/base/ftobjs.c&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="gi"&gt;+++ b/src/base/ftobjs.c&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="gu"&gt;&amp;#64;&amp;#64; -3074,6 +3074,7 &amp;#64;&amp;#64;&lt;/span&gt;&lt;span class="w"&gt;
 &lt;/span&gt;    FT_Int   i;&lt;span class="w"&gt;
 &lt;/span&gt;    FT_Long  w, h;&lt;span class="w"&gt;
 
&lt;/span&gt;&lt;span class="gi"&gt;+    ignore_width = 1;&lt;/span&gt;&lt;span class="w"&gt;
 
 &lt;/span&gt;    if ( !FT_HAS_FIXED_SIZES( face ) )&lt;span class="w"&gt;
 &lt;/span&gt;      return FT_THROW( Invalid_Face_Handle );&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="gu"&gt;&amp;#64;&amp;#64; -3101,8 +3102,6 &amp;#64;&amp;#64;&lt;/span&gt;&lt;span class="w"&gt;
 &lt;/span&gt;      FT_Bitmap_Size*  bsize = face-&amp;gt;available_sizes + i;&lt;span class="w"&gt;
 
 
&lt;/span&gt;&lt;span class="gd"&gt;-      if ( h != FT_PIX_ROUND( bsize-&amp;gt;y_ppem ) )&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="gd"&gt;-        continue;&lt;/span&gt;&lt;span class="w"&gt;
 
 &lt;/span&gt;      if ( w == FT_PIX_ROUND( bsize-&amp;gt;x_ppem ) || ignore_width )&lt;span class="w"&gt;
 &lt;/span&gt;      {
&lt;/pre&gt;
&lt;p&gt;What this patch does is force freetype to return the first bitmap option inside
the font, even if it doesn't have the size we asked for.&lt;/p&gt;
&lt;p&gt;With that change done, I then got a different error, now in matplotlib:&lt;/p&gt;
&lt;pre class="code literal-block"&gt;
Traceback (most recent call last):
  File &amp;quot;/home/nfraprado/emoji.py&amp;quot;, line 3, in &amp;lt;module&amp;gt;
    matplotlib.font_manager.fontManager.addfont('/usr/share/fonts/noto/NotoColorEmoji.ttf')
  File &amp;quot;/home/nfraprado/matplotlib/lib/matplotlib/font_manager.py&amp;quot;, line 1134, in addfont
    prop = ttfFontProperty(font)
  File &amp;quot;/home/nfraprado/matplotlib/lib/matplotlib/font_manager.py&amp;quot;, line 545, in ttfFontProperty
    raise NotImplementedError(&amp;quot;Non-scalable fonts are not supported&amp;quot;)
NotImplementedError: Non-scalable fonts are not supported
&lt;/pre&gt;
&lt;p&gt;So, only now is matplotlib complaining that the font isn't scalable, which
should have been the error to begin with... Anyway, I was curious to see if this
would all work in the end, so I just removed the check:&lt;/p&gt;
&lt;pre class="code diff literal-block"&gt;
&lt;span class="gh"&gt;diff --git a/lib/matplotlib/font_manager.py b/lib/matplotlib/font_manager.py&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="gh"&gt;index f57fc9c051b0..08d8c28752cb 100644&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="gd"&gt;--- a/lib/matplotlib/font_manager.py&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="gi"&gt;+++ b/lib/matplotlib/font_manager.py&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="gu"&gt;&amp;#64;&amp;#64; -541,8 +541,6 &amp;#64;&amp;#64; def ttfFontProperty(font):&lt;/span&gt;&lt;span class="w"&gt;
 &lt;/span&gt;    #  Length value is an absolute font size, e.g., 12pt&lt;span class="w"&gt;
 &lt;/span&gt;    #  Percentage values are in 'em's.  Most robust specification.&lt;span class="w"&gt;
 
&lt;/span&gt;&lt;span class="gd"&gt;-    if not font.scalable:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="gd"&gt;-        raise NotImplementedError(&amp;quot;Non-scalable fonts are not supported&amp;quot;)&lt;/span&gt;&lt;span class="w"&gt;
 &lt;/span&gt;    size = 'scalable'&lt;span class="w"&gt;
 
 &lt;/span&gt;    return FontEntry(font.fname, name, style, variant, weight, stretch, size)
&lt;/pre&gt;
&lt;p&gt;And finally got some output:&lt;/p&gt;
&lt;img alt="{image}/emojis-bw-unscaled.png" src="/images/matplotlib-emojis/emojis-bw-unscaled.png" /&gt;
&lt;p&gt;The emojis are black and white, and not the right size, but it's a step forward.
At this point, what I was seeing looked a similar to what I had seen in &lt;a class="reference external" href="https://towardsdatascience.com/how-i-got-matplotlib-to-plot-apple-color-emojis-c983767b39e0"&gt;a blog
post&lt;/a&gt; and the linked &lt;a class="reference external" href="https://github.com/matplotlib/matplotlib/issues/12830"&gt;issue on Github&lt;/a&gt; while searching around. The issue
there is about the TTC format, so not the same thing, but the workaround to use
a different backend that has better support for emojis, &lt;a class="reference external" href="https://github.com/matplotlib/mplcairo"&gt;mplcairo&lt;/a&gt;, did sound
promising.&lt;/p&gt;
&lt;p&gt;So I installed mplcairo with pip and set the script to use it with&lt;/p&gt;
&lt;pre class="code python literal-block"&gt;
&lt;span class="n"&gt;matplotlib&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;use&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;module://mplcairo.gtk&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;
&lt;p&gt;And this was the output:&lt;/p&gt;
&lt;img alt="{image}/emojis-colored-scaled.png" src="/images/matplotlib-emojis/emojis-colored-scaled.png" /&gt;
&lt;p&gt;So the emojis actually rendered colored and scaled correctly with this backend!
The only caveat was that the text on the Y-axis was rotated, but a quick search
revealed that &lt;a class="reference external" href="https://github.com/matplotlib/mplcairo/issues/35"&gt;this was already fixed on the main branch&lt;/a&gt;, so I reinstalled
mplcairo from git, and everything was golden!&lt;/p&gt;
&lt;p&gt;I did think this investigation was going to end up in some fix being sent to
either the font, freetype, or matplotlib, but with my current understanding of
the issue, I'm not entirely sure what would be the right fix here. I'd need to
investigate this a bit further to know, but given that I've managed to get it
working for my purposes, granted with some hacky patches, I don't see myself
looking more into this for now.&lt;/p&gt;
&lt;p&gt;That said, my understanding is that mplcairo does the font scaling on its own
for fixed size (emoji) fonts, so maybe making mplcairo check the available font
sizes and ask freetype for the one available that is closer to the size it'll
draw with, could be a way to get rid of the freetype patch. As for the
matplotlib patch, maybe fixed size fonts could be allowed when the mplcairo
backend is being used, since it clearly knows how to handle them. But again,
more investigation needed to conclude anything.&lt;/p&gt;
&lt;p&gt;In any case, with the two patches shown here and the mplcairo backend, I was
able to generate the plot with the most frequently used emojis at the end of the
&lt;a class="reference external" href="/post/blog-statistics-after-two-years.html"&gt;"Blog statistics after two years" post&lt;/a&gt;. The complete script that generated the plot can be
seen &lt;a class="reference external" href="https://codeberg.org/nfraprado/blog/src/commit/f6c10780301d53661eff67052e180b9705d94610/extra/plot-top-emoji.py"&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Finally, for reproducibility purposes, it's worth to say that all of this was
tested with the packages in the following commits:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;mplcairo: &lt;a class="reference external" href="https://github.com/matplotlib/mplcairo/commit/74c27c3dbd54c6c59fa0fd212d9c2bacd3275649"&gt;74c27c3dbd54&lt;/a&gt; (&amp;quot;Tweak path search in build-windows-wheel.&amp;quot;)&lt;/li&gt;
&lt;li&gt;matplotlib: &lt;a class="reference external" href="https://github.com/matplotlib/matplotlib/commit/60ae76b6b5b071e45d1ab652464f68a592cf977d"&gt;60ae76b6b5b0&lt;/a&gt; (&amp;quot;Merge pull request #23243 from timhoffm/take-over-22839&amp;quot;)&lt;/li&gt;
&lt;li&gt;freetype: &lt;a class="reference external" href="https://gitlab.freedesktop.org/freetype/freetype/-/commit/e7482ff4c2a39e0e6bcf32b90ccfbfdd0f8ef5e6"&gt;e7482ff4c2a3&lt;/a&gt; (&amp;quot;* src/lzw/ftzopen.c (ft_lzwstate_stack_grow): Cosmetic macro change.&amp;quot;)&lt;/li&gt;
&lt;/ul&gt;
</content><category term="2022"/><category term="matplotlib"/><category term="emoji"/></entry><entry><title>Blog statistics after two years</title><link href="https://nfraprado.net/post/blog-statistics-after-two-years.html" rel="alternate"/><published>2022-06-20T00:00:00-03:00</published><updated>2022-06-20T00:00:00-03:00</updated><author><name>Nícolas F. R. A. Prado</name></author><id>tag:nfraprado.net,2022-06-20:/post/blog-statistics-after-two-years.html</id><summary type="html">&lt;p&gt;Wow, it's been two years since I've started this blog! I'm really happy that
I've still managed to keep up the one post per month. Hopefully I'll be able to
keep it up for the third one!&lt;/p&gt;
&lt;p&gt;For this post I thought it would be fun to look through some …&lt;/p&gt;</summary><content type="html">&lt;p&gt;Wow, it's been two years since I've started this blog! I'm really happy that
I've still managed to keep up the one post per month. Hopefully I'll be able to
keep it up for the third one!&lt;/p&gt;
&lt;p&gt;For this post I thought it would be fun to look through some statistics on what
I've done on the blog so far. To make this possible &lt;a class="reference external" href="https://codeberg.org/nfraprado/blog/commit/c372aa64ce2049d42df0df4f908955905e89411b"&gt;I've created a 'stats'
plugin&lt;/a&gt; for pelican that generates the statistics I'm interested in. &lt;a class="reference external" href="https://codeberg.org/nfraprado/blog/commit/f6c10780301d53661eff67052e180b9705d94610"&gt;I then
wrote python scripts using matplotlib&lt;/a&gt; to plot the collected data.&lt;/p&gt;
&lt;div class="section" id="plots-stats"&gt;
&lt;h2&gt;Plots &amp;amp; Stats&lt;/h2&gt;
&lt;p&gt;I've managed to keep up with the monthly posting schedule, but I haven't been
able to always post on the 20th of each month as I intended, and it shows here:&lt;/p&gt;
&lt;img alt="{image}/dom-publish_en.png" src="/images/2years-stats/dom-publish_en.png" /&gt;
&lt;p&gt;(Open the image in a new tab to see it better)&lt;/p&gt;
&lt;p&gt;In the beginning I hadn't yet decided on a date to post each month, but starting
with 2020's September post I settled on the 20th of each month and I managed to
more or less keep that up until May of the following year. After that I've
almost never again posted on the 20th, and there's even some kind of upwards
slope as the posts have been sliding to the end of the months.&lt;/p&gt;
&lt;p&gt;This other plot shows the total accumulated count on each day of the month:&lt;/p&gt;
&lt;img alt="{image}/dom-publish-count_en.png" src="/images/2years-stats/dom-publish-count_en.png" /&gt;
&lt;p&gt;The 20th is still the day of the month I've posted the most on, with 8 on total,
but there are more posts evenly distributed in the days after it, totalling 17!
That's to say only about one third of my posts were published on the 20th like I
intended.&lt;/p&gt;
&lt;p&gt;This doesn't bother me much though. It would've been nice to keep the posts
consistently going up on the same day, but posting every single month, whatever
the date, is much more important to me, and I'm very happy I've been successful
on that.&lt;/p&gt;
&lt;p&gt;Changing subjects a bit, I always wanted to know the word count of the posts, so
here it is:&lt;/p&gt;
&lt;img alt="{image}/word-count_en.png" src="/images/2years-stats/word-count_en.png" /&gt;
&lt;p&gt;There's no clear tendency, which is what I expected. The smallest ever post was
the &lt;a class="reference external" href="/post/setting-up-mbsync-to-work-with-xoauth2.html"&gt;"Setting up mbsync to work with XOAUTH2" post&lt;/a&gt; with 553 words in English and 567 words in
Portuguese, while the largest one was the &lt;a class="reference external" href="/post/managing-my-tasks-using-vit.html"&gt;"Managing my tasks using VIT" post&lt;/a&gt; with a whopping
2727 words in English and 2747 words in Portuguese. The second largest post is
the &lt;a class="reference external" href="/post/owning-my-kindle.html"&gt;"Owning my Kindle" post&lt;/a&gt; not too far below, while all the rest are
below the 2000 words mark.&lt;/p&gt;
&lt;p&gt;This graph also allows for comparing the word count between English and
Portuguese, and it's clear that the Portuguese version is almost always slightly
bigger. This agrees with my perception, as multiple times I've increased the
sentence's length as I try to express the same thing in Portuguese which in
English I was able to express with a common technical term.&lt;/p&gt;
&lt;p&gt;In total, all English posts sum up to 32718 words, while all Portuguese ones sum
up to 33488 words. So my Portuguese posts are in average 2% larger, which is
honestly less than I expected.&lt;/p&gt;
&lt;p&gt;It is worth mentioning that this word count is done at the final generation
stage of the article, when it is already in its HTML form, meaning that all the
content that shows when it's published is there, with the difference that the
HTML tags that could add to the count are stripped. In practice, this means that
the total word count is the post's text plus the code blocks. So one important
element that doesn't show up in this plot are the images, which can be seen
here:&lt;/p&gt;
&lt;img alt="{image}/num-images_en.png" src="/images/2years-stats/num-images_en.png" /&gt;
&lt;p&gt;Not all of the posts have images, but some of them have quite a few! In fact,
the &lt;a class="reference external" href="/post/owning-my-kindle.html"&gt;"Owning my Kindle" post&lt;/a&gt;, which was the second largest post, is the
one with the most images, 16 in total!&lt;/p&gt;
&lt;p&gt;Now, this part is very arbitrary, but I wanted to account for the number of
images in a post when comparing the amount of content in each one. They say a
picture is worth a thousand words, but for my purposes that seems a bit
excessive. Instead counting each image as 75 words, a normal paragraph, seems
reasonable. I also decided to make GIFs be worth three times as much, 225 words,
since their dynamic nature makes them much richer. With this in mind, I plotted
the total content estimate of all posts:&lt;/p&gt;
&lt;img alt="{image}/total-content_en.png" src="/images/2years-stats/total-content_en.png" /&gt;
&lt;p&gt;Again, the numbers are totally subjective, and this still doesn't account for
the audio files I've composed and linked to in the &lt;a class="reference external" href="/post/learning-music-theory-by-writing-melodies.html"&gt;"Learning music theory by writing melodies" post&lt;/a&gt; nor
for the code that I wrote on some repository and just linked in the post, like
in the &lt;a class="reference external" href="/post/keeping-track-of-my-packages.html"&gt;"Keeping track of my packages" post&lt;/a&gt;. But given its limitations, I'd say this is much
closer to my perception of how much content each post has.&lt;/p&gt;
&lt;p&gt;A notable diference is that on this new plot, the
&lt;a class="reference external" href="/post/owning-my-kindle.html"&gt;"Owning my Kindle" post&lt;/a&gt; became the post with the most content.&lt;/p&gt;
&lt;p&gt;Anyway, going back to more objective metrics, I plotted the number of links to
other posts, or cross-references if you will, each post had:&lt;/p&gt;
&lt;img alt="{image}/num-article-links_en.png" src="/images/2years-stats/num-article-links_en.png" /&gt;
&lt;p&gt;So most posts don't link to others (to be expected), and the ones that do only
have a couple links at most. That said, this very post would be much higher than
the rest on that plot, but I'd say it is an exception, since it's an
introspective post about the blog.&lt;/p&gt;
&lt;p&gt;What about any links?&lt;/p&gt;
&lt;img alt="{image}/num-links_en.png" src="/images/2years-stats/num-links_en.png" /&gt;
&lt;p&gt;Every post has at least one link, which is not surprising in the least, this is
the web after all!&lt;/p&gt;
&lt;p&gt;And finally, the most important question, how many emojis??&lt;/p&gt;
&lt;img alt="{image}/num-emojis_en.png" src="/images/2years-stats/num-emojis_en.png" /&gt;
&lt;p&gt;Not as common as links (thankfully?), but not bad either. I do seem to be
getting more consistent in my emojis, whatever that means.&lt;/p&gt;
&lt;p&gt;But how good is it to know how many emojis are being used if we can't see which
ones?&lt;/p&gt;
&lt;img alt="{image}/top-emoji_en.png" src="/images/2years-stats/top-emoji_en.png" /&gt;
&lt;p&gt;Of course the most common is the smiley face, hard not to smile when you're
talking about something that you're interested in. On second place we have the
tongue face for the funny moments. And a sprinkle of other random emojis.&lt;/p&gt;
&lt;p&gt;This plot was way harder to do than it seems, but more about it next month
(probably).&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="conclusion"&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;Even though it can be difficult to put myself to write about something every
month, I really do enjoy the result. This monthly ritual turned out to be
therapeutic to me, as I get the chance to build a new brick in this safe space
that is my blog, so I feel like I'm advancing in my life even if nothing else
happens.&lt;/p&gt;
&lt;p&gt;The need for a post every month also encourages me to actually keep doing
things that interest me, otherwise I'll end up without anything to write about!&lt;/p&gt;
&lt;/div&gt;
</content><category term="2022"/><category term="blog-birthday"/><category term="blog"/></entry><entry><title>Discovering the comfort of loudspeakers</title><link href="https://nfraprado.net/post/discovering-the-comfort-of-loudspeakers.html" rel="alternate"/><published>2022-05-27T00:00:00-03:00</published><updated>2022-05-27T00:00:00-03:00</updated><author><name>Nícolas F. R. A. Prado</name></author><id>tag:nfraprado.net,2022-05-27:/post/discovering-the-comfort-of-loudspeakers.html</id><summary type="html">&lt;p&gt;In 2014, while on a school trip to Germany, I bought a Razer Tiamat headset. The
sound was great and for the most part it was comfortable. Its only issue was the
clamping force, which was a bit too much, and gave me headaches after long hours
of use.&lt;/p&gt;
&lt;p&gt;I …&lt;/p&gt;</summary><content type="html">&lt;p&gt;In 2014, while on a school trip to Germany, I bought a Razer Tiamat headset. The
sound was great and for the most part it was comfortable. Its only issue was the
clamping force, which was a bit too much, and gave me headaches after long hours
of use.&lt;/p&gt;
&lt;p&gt;I made great use of this headset and it served me all the way up to 2021.
By that point, the leather on the earpads was mostly gone, and the jack
connector had already broken twice and needed resoldering a new one. One of the
times the contact on one of the wires wasn't very good, which caused one side
to be louder than the other and needed compensating in software, but then the
compensation needed changed with changes in volume, which drove me crazy and I
remember being incredibly happy when I finally fixed it.&lt;/p&gt;
&lt;p&gt;So by 2021 a new headset was already overdue. Since the clamping force was my
main pain point (quite literally), I was going to try to find one that had the
lowest possible while still being reasonably priced.&lt;/p&gt;
&lt;div class="section" id="searching-for-a-comfortable-headset"&gt;
&lt;h2&gt;Searching for a comfortable headset&lt;/h2&gt;
&lt;p&gt;Searching online I found a site called Rtings that reviewed and ranked
headphones, with one of the criteria being the clamping force. I filtered their
list by this criterion, and settled on the HyperX Cloud Alpha since it had one
of the lowest clamping force and wasn't very expensive.&lt;/p&gt;
&lt;p&gt;But when it arrived, I was very disappointed. The clamping force was much higher
than what I had on the Tiamat and I easily got headaches after just a couple
hours using it. I tried flexing it overnight, but that didn't seem to make a
difference. So I eventually just started avoiding to use the headset, and used
the builtin notebook speakers instead, which sound awful, but they at least
aren't painful.&lt;/p&gt;
&lt;p&gt;A few months later I had moved to the US and had a much wider and accessible
array of options at my disposal, so I decided to try it again. I wasn't going to
get it wrong a second time, so I made sure to try out as many headphones as I
could at physical stores, B&amp;amp;H being the biggest one I knew.&lt;/p&gt;
&lt;p&gt;To my surprise however, almost all of them had a big enough clamping force
to be uncomfortable for me. Even the Bose QuietComfort 35, which from my
research was one of the most comfortable headphones available, was a bit too
tight for me. I did try some that were actually okay, like Sony Mdr7506,
Phillips HP9500 and Koss UR20. And funnily enough they were also cheaper than
most others. But these headphones seemed like they were so loose they were about
to fall off. There didn't seem to be a sweet spot for the clamping force.&lt;/p&gt;
&lt;p&gt;At this point, I started thinking that maybe my problem was inherent to
headphones. I had already thought of earbuds, but the issue with these is that
they deform inside your ear, causing a different kind of pain. Then I suddenly
thought about loudspeakers. Could they be my answer?&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="searching-for-a-good-speaker"&gt;
&lt;h2&gt;Searching for a good speaker&lt;/h2&gt;
&lt;p&gt;As I researched I found a very well researched and tested &lt;a class="reference external" href="https://www.nytimes.com/wirecutter/reviews/best-computer-speakers/"&gt;Speakers ranking from
Wirecutter&lt;/a&gt;. Looks like this is one of the most well known series of rankings,
but I hadn't come across it before so I was happy to find it.&lt;/p&gt;
&lt;p&gt;I came very close to ordering one of their top picks, but I kept feeling like a
subwoofer would be more important to me than the flat response they were
prioritizing, since my use case is almost exclusively listening to music, and
not producing.&lt;/p&gt;
&lt;p&gt;They did have as their &amp;quot;Also great&amp;quot; the Klipsch ProMedia 2.1 THX, which has a
subwoofer. As I researched around, I noticed that while other rankings had
different top picks, this speaker was also consistently appearing on other
ranks. And it seems like this speaker has been around for a long time, so if
it's still relevant in these rankings, then it must be really good.&lt;/p&gt;
&lt;p&gt;The downsides for the Klipsch usually mentioned the need for extra space for its
bulky subwoofer and lack of conectivity eg. bluetooth - the audio input is
exclusively through a 3.5mm jack cable. I didn't have any problem with the extra
space needed: The subwoofer goes on the floor, and I have plenty of free space
there! As for the jack input only: I was going to keep it constantly
connected to the Line Out port on my docking station, so no worries here either.&lt;/p&gt;
&lt;p&gt;Finally, to get a better feeling for how important a subwoofer would be for me,
I opened some songs that I liked and that had a deep bass in them in
Audacity and used the Filter Curve effect to try and imitate the frequency
response graph that the Wirecutter article showed for the speakers with no
subwoofer to try and see how it would sound like. This proved very useful and it
was clear to me that without the lower frequencies, these songs lost a lot of
their substance. So I concluded that I really wanted a subwoofer, and the
Klipsch seemed like the best speaker with one, so I went for it.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="the-klipsch-promedia-2-1-thx"&gt;
&lt;h2&gt;The Klipsch ProMedia 2.1 THX&lt;/h2&gt;
&lt;p&gt;And oh boy... This has to be one of my happiest purchases in life. First of all,
because of the change from headphone to loudspeaker. I can't believe I can
finally listen to music &lt;em&gt;all day long&lt;/em&gt; without feeling anything &lt;em&gt;at all&lt;/em&gt; on my
head. This is &lt;em&gt;magic&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;Second, the subwoofer really brings the music to life. Not &lt;em&gt;every&lt;/em&gt; song makes
use of it, but more songs than I thought. You can mainly hear it (and feel it!)
from the bass and the percussion in the songs. There is a subwoofer dial and I
keep turning it down and up again to compare the difference it makes on the
sound and it's impressive.&lt;/p&gt;
&lt;p&gt;As for the downsides, there's a bright power LED between the dials and no ON/OFF
switch, which means this LED is always on. But a small piece of tape to cover it
easily solves the issue.&lt;/p&gt;
&lt;p&gt;Another thing is that by having such a good speaker fixed to my desk, I do
miss it when I want to take my notebook to a different room. If I could somehow
bring it with me in a portable way, that would be wonderful... But then we're
back at the concept of headphones and we already know where that path leads 😝.
I also miss a bit the sound isolation (in both ways) that you get from
headphones, but that's way less important than comfort to me.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="conclusion"&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;I find it interesting that when I was a kid, the computer in my house had a pair
of speakers as was usual. But since then I've only had, and seen other people
use, headphones, so I subconsciously considered it the only option. Realizing
that's not the case, even though loudspeakers do seem a lot less common
nowadays, and going back to them now feels poetic in a way.&lt;/p&gt;
&lt;p&gt;I do still have my hated headset and I use it for calls, since the echo from the
speakers would be very bad. But as soon as the call is over I'm &lt;em&gt;really&lt;/em&gt; happy
to get the headset off and get back to the comfort of my speakers 😌.&lt;/p&gt;
&lt;/div&gt;
</content><category term="2022"/><category term="buy"/><category term="comfort"/><category term="sound"/></entry><entry><title>Learning from SerenityOS</title><link href="https://nfraprado.net/post/learning-from-serenityos.html" rel="alternate"/><published>2022-04-29T00:00:00-03:00</published><updated>2022-04-29T00:00:00-03:00</updated><author><name>Nícolas F. R. A. Prado</name></author><id>tag:nfraprado.net,2022-04-29:/post/learning-from-serenityos.html</id><summary type="html">&lt;p&gt;One day I was scrolling through Reddit as usual, when I saw a post linking to
this blog post: &lt;a class="reference external" href="https://awesomekling.github.io/I-quit-my-job-to-focus-on-SerenityOS-full-time/"&gt;I quit my job to focus on SerenityOS full time&lt;/a&gt;. I was
intrigued by the backstory, the premise of this OS, and also by the fact that
its development was &lt;a class="reference external" href="https://www.youtube.com/c/AndreasKling/videos"&gt;being …&lt;/a&gt;&lt;/p&gt;</summary><content type="html">&lt;p&gt;One day I was scrolling through Reddit as usual, when I saw a post linking to
this blog post: &lt;a class="reference external" href="https://awesomekling.github.io/I-quit-my-job-to-focus-on-SerenityOS-full-time/"&gt;I quit my job to focus on SerenityOS full time&lt;/a&gt;. I was
intrigued by the backstory, the premise of this OS, and also by the fact that
its development was &lt;a class="reference external" href="https://www.youtube.com/c/AndreasKling/videos"&gt;being recorded on Youtube&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Now, almost one year later, I have watched almost every video Andreas has posted
in his channel since and a few of the older ones too. His videos are so good,
they're entertaining, inspiring and I've learned so much from them. And he's
such a nice human being too. The fact that such a positive project came out of
his own therapy is just perfect. I think he definitely found his calling in life
and I'm really happy for him.&lt;/p&gt;
&lt;p&gt;SerenityOS is such an interesting project itself. There's code for the kernel,
libraries, services and applications, all there in a single repository. It
invites you to become familiar with the whole system and to see how each
component interacts with the others and fits with the whole. Furthermore there's
such a huge variety on what kind of problems each component is trying to solve
that it feels like there's always something interesting to explore on Serenity.&lt;/p&gt;
&lt;p&gt;But having everything together isn't only helpful for learning the code base.
When you make the whole system, it's possible to do some really cool
integrations. Some of my favorites features on Serenity are:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;All data exposed by the kernel through procfs-like files is in JSON!&lt;/li&gt;
&lt;li&gt;The Profile Viewer app is not only able to show system-wide profiles with
tree-view stack traces but also even signposts set by traced applications
during interesting events!&lt;/li&gt;
&lt;li&gt;There's a nice markup language to design the GUI for the applications, GML,
and the GML Playground app shows you a live preview of the GUI as you write!&lt;/li&gt;
&lt;li&gt;On &lt;em&gt;every&lt;/em&gt; application (unless it opted-out) it's possible to open a command
pallete with all commands on that app!&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Another advantage that comes from making the entire stack is that changing APIs
is much easier, since they control both sides. That allows faster iteration and
for better ways to do things to evolve over time without needing to maintain
older iterations for compatibility concerns. That said, that flexibility doesn't
extend to the ABI and interactions with the user. And although so far it has
been the case that these haven been freely changed as well, I wonder how much of
this is due to SerenityOS still being seen as Work-In-Progress and there not
being daily users yet. But perhaps it being &amp;quot;a system by us, for us&amp;quot;, the
ability to iterate and improve upon even the ABI will be seen as more important
than keeping it stable. I guess we'll see.&lt;/p&gt;
&lt;p&gt;When it comes to contributing to the project myself, so far I've done just a
couple ones, mainly &lt;a class="reference external" href="https://github.com/SerenityOS/serenity/pull/12841"&gt;the most basic support for showing album covers in the
sound player&lt;/a&gt; and &lt;a class="reference external" href="https://github.com/SerenityOS/serenity/pull/12764"&gt;adding proper support for multiple keyboard layouts in the
KeyboardSettings app&lt;/a&gt;. But I had a lot of fun working on them, and look
forward to contributing more.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Edit&lt;/strong&gt;: on the next day from publishing this post my contribution on Sound
Player was showcased in &lt;a class="reference external" href="https://youtu.be/yUmHEYs5n34?t=2105"&gt;SerenityOS's April Update video&lt;/a&gt;! 😃&lt;/p&gt;
&lt;p&gt;One thing that really got me thinking is Serenity's goal of having 1990's-like
GUI. When I started to use and learn about Linux some years ago, I was
immediately attracted by CLI and TUI programs and have preferred them since.
They represented a very different paradigm from the GUI programs that I was used
to in the Windows world. On the terminal there was no fear of exposing all the
functionalities in a very flexible manner, there was much more room for
customizations and performance tended to be better.&lt;/p&gt;
&lt;p&gt;But after seeing Serenity's approach, I think my disregard for GUI applications
has been a bit misguided. With the right mindset, GUI programs can be quite
flexible, customizable and performant. And on top of that they can look
consistent and be aesthetically pleasing. What else do you want from software?
In short, I'm actually confident now that I'd prefer using GUIs for most tasks,
it's just that the ones out there don't tend to focus on the right things for
me.&lt;/p&gt;
&lt;p&gt;SerenityOS still does feel a bit like a playground. And it is fun to play around
with. But I also agree with a lot of its goals, and it is really rapidly
growing, so it is a very promising OS to me. On one hand, I wish it was ready
for everyday use so I could switch to it already, but on the other, that would
take all the fun of getting there (both when contributing myself and also seeing
Andreas' videos!). Plus it's easier to contribute to it if there are still a lot
of rough edges 🙂.&lt;/p&gt;
</content><category term="2022"/><category term="serenityos"/></entry><entry><title>Running LineageOS on my Nexus 5X</title><link href="https://nfraprado.net/post/running-lineageos-on-my-nexus-5x.html" rel="alternate"/><published>2022-03-30T00:00:00-03:00</published><updated>2022-03-30T00:00:00-03:00</updated><author><name>Nícolas F. R. A. Prado</name></author><id>tag:nfraprado.net,2022-03-30:/post/running-lineageos-on-my-nexus-5x.html</id><summary type="html">&lt;p&gt;This month marks one year since I bought a Nexus 5X and started using LineageOS,
so I thought I'd share my experience.&lt;/p&gt;
&lt;div class="section" id="setting-up"&gt;
&lt;h2&gt;Setting up&lt;/h2&gt;
&lt;p&gt;About one year ago, &lt;a class="reference external" href="https://andrealmeid.com/"&gt;a friend&lt;/a&gt; told me he found a really good deal for a Nexus
5X online. The Nexus 5X is an old phone …&lt;/p&gt;&lt;/div&gt;</summary><content type="html">&lt;p&gt;This month marks one year since I bought a Nexus 5X and started using LineageOS,
so I thought I'd share my experience.&lt;/p&gt;
&lt;div class="section" id="setting-up"&gt;
&lt;h2&gt;Setting up&lt;/h2&gt;
&lt;p&gt;About one year ago, &lt;a class="reference external" href="https://andrealmeid.com/"&gt;a friend&lt;/a&gt; told me he found a really good deal for a Nexus
5X online. The Nexus 5X is an old phone by now, launched in 2015, so it tends to
be fairly cheap, even though it's still a rather decent phone by today's
standards. The offer he found was for a &lt;em&gt;new&lt;/em&gt; Nexus 5X, which came inside the
original box, perfectly conserved, and still cost only R$ 300. Really good deal.&lt;/p&gt;
&lt;p&gt;At this point I had already been using Linux on my computer for some years, but
my phone, arguably the machine that has access to the biggest amount of personal
data, was still running Android, which is an OS that is not entirely FOSS, and
that bothered me quite a bit.&lt;/p&gt;
&lt;p&gt;I had already had the desire to switch to LineageOS, the FOSS alternative to
Android, for some time now, but since it was my only phone it felt like putting
too much at stake. With the possibility of getting a new Nexus 5X suddenly I saw
the opportunity of having a tried and tested (and loved) phone with LineageOS,
and if all went wrong, my older phone would still be there with everything the
same way it was. So I decided to get a Nexus for me as well.&lt;/p&gt;
&lt;p&gt;I followed &lt;a class="reference external" href="https://andrealmeid.com/post/2020-11-23-bullhead-los/"&gt;my friend's Nexus 5X guide&lt;/a&gt; and had some personal help from him in
order to understand the required software components to be able to run a
LineageOS installation completely de-Googled. In my case the phone was brand new
so I could use the image without the BLOD patch and enjoy full Nexus 5X
performance.&lt;/p&gt;
&lt;p&gt;The installation itself was easy to follow (granted I had a guide and a mentor).
And at the end I had the phone with LineageOS, MicroG and Magisk, completely
de-Googled and yet able to install software that relied on Google Services when
needed.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="issues"&gt;
&lt;h2&gt;Issues&lt;/h2&gt;
&lt;p&gt;Overall the experience is great, but there are two main issues:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;strong&gt;Frequent camera crashes&lt;/strong&gt;: basically every time I open the camera, it
crashes on the first try, and I need to reopen it and then it works. Annoying,
but not enough yet for me to go fix it.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Flaky GPS support&lt;/strong&gt;: GPS was the hardest thing to get working. I remember
finding some pointers &lt;a class="reference external" href="https://blog.eowyn.net/unifiednlp/"&gt;in this post&lt;/a&gt;, and finally got it working by simply
enabling the &amp;quot;Mozilla Location Service&amp;quot; and &amp;quot;WiFi Location Service&amp;quot; location
modules, but it didn't always work and would have an initial delay before it
started working. And then I moved countries and can't get it working at all
anymore...&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;But those are software issues and could be eventually fixed. There is however
one single hardware limitation on the Nexus 5X that made me disappointed as soon
as I realized: &lt;strong&gt;There's only 16GB of internal storage and no external storage
card slot&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;For the apps themselves 16GB can actually be ok, but for taking pictures or
videos and storing music, it wasn't enough.&lt;/p&gt;
&lt;div class="section" id="searching-for-more-space"&gt;
&lt;h3&gt;Searching for more space&lt;/h3&gt;
&lt;p&gt;But I didn't give up immediately on the matter. The phone was so close to
perfect, I needed to find a workaround for the lack of storage space.&lt;/p&gt;
&lt;p&gt;The Nexus 5X has an USB-C OTG port, so I realized I could buy a USB-C
flash drive, the smallest possible so that it wouldn't be too intrusive, in
order to effectively expand the storage space.&lt;/p&gt;
&lt;p&gt;After researching for a bit, the most compact USB-C flash drive I found was the
&lt;a class="reference external" href="https://www.amazon.com/Kingston-Digital-32GB-Traveler-DTDUO3C/dp/B01M59PHE4"&gt;Kingston DTDUO3C/128GB&lt;/a&gt;. So I went ahead and bought it. Right after it
arrived I noticed that it comes with a little keyring and that my phone case had
a slot for that, so I tied it there and thought it looked cute. It's also way
easier to keep it always together with the phone this way. That said there are
some drawbacks: the USB drive sometimes gets stuck in my pocket, and also hits
the screen. This is how it looks:&lt;/p&gt;
&lt;img alt="{image}/usb_disconnected.jpg" src="/images/nexus5x/usb_disconnected.jpg" /&gt;
&lt;img alt="{image}/usb_connected.jpg" src="/images/nexus5x/usb_connected.jpg" /&gt;
&lt;p&gt;However, as I inserted the flash drive I was disappointed to see that my music
player didn't recognize any of the music in there. It seemed that the flash
drive was just like a dumb storage and I could only move files to/from it from
the file manager.&lt;/p&gt;
&lt;p&gt;But given that this was weird, I thought there needed to be a way around it.
After all, it's just an arbitrary limitation imposed by Android. After a bit of
research, sure enough, &lt;a class="reference external" href="https://forum.xda-developers.com/t/enable-apps-to-read-otg-usb-contents-in-marshmallow-nougat.3539389/"&gt;a post in the XDA forum came to rescue&lt;/a&gt; with a
one-liner solution:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;su
sm&lt;span class="w"&gt; &lt;/span&gt;set-force-adoptable&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;true&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;And just like that, reinserting the USB flash drive made the music player app
hurringly start indexing all the music files it had just found in the flash
drive. It worked!&lt;/p&gt;
&lt;p&gt;There is one big caveat with this however, which is that I need to remember to
always insert the USB drive before opening the music player, and closing it
before removing the drive. Otherwise the player no longer sees any music on the
device, and the next time I plug the drive in, it spends a couple minutes
indexing all the files again (and crashing a few times during the process).&lt;/p&gt;
&lt;p&gt;Given that I'd need my music files (and possibly photos) to be synchronized with
the computer, I was also interested in getting Syncthing working with folders
in the USB drive.&lt;/p&gt;
&lt;p&gt;To my surprise it didn't work at first: Syncthing has read-only permission to
external storage. But I quickly found out that giving it root permission removes
this limitation, which can be done through Magisk. Not the ideal solution, but
at least this gives a working setup for music!&lt;/p&gt;
&lt;p&gt;I was also curious to see if I could have the camera save photos and videos
directly to the USB drive. This would lift most of my concerns about being short
on storage.&lt;/p&gt;
&lt;p&gt;In order to not fiddle with the main camera app settings, I downloaded the
Simple Camera app to try this out on, and configured it to save to a folder in
the USB storage. To my surprise it simply asked for permission to have
write-access to the USB, not requiring to be run as root like Syncthing did.&lt;/p&gt;
&lt;p&gt;I then tried recording a video on it. On the first try it threw an error and
asked me to select the image quality and I lowered it to 1080p. I think the
write speed on the USB couldn't cope with whatever the original resolution was.&lt;/p&gt;
&lt;p&gt;After that though, it did manage to successfully take pictures and record
videos. However right after I stop recording a video the app closes, but the
file does seem to be flushed correctly to the USB storage and sync'ed with my
computer.&lt;/p&gt;
&lt;p&gt;But actually so far I have avoided using the USB storage for the camera
(by using the original camera app), because, unlike for music, taking
pictures/videos is time-sensitive, and I don't want to risk having my pictures
fail to save or take multiple tries (the app crashing is enough already). In any
case it's good to know that it works, to some extent, for when I want to use the
camera and the internal storage is full.&lt;/p&gt;
&lt;div class="section" id="usb-as-internal-storage"&gt;
&lt;h4&gt;USB as internal storage&lt;/h4&gt;
&lt;p&gt;But something else happened when I set that &lt;code class="docutils literal"&gt;force-adoptable&lt;/code&gt; configuration.
While browsing in the settings, it looked like I now could actually format the
flash drive as internal storage. This would mean that the flash drive memory
would be used transparently, even to hold app instalations. But a question came
to mind: &amp;quot;What would then happen when I removed the flash drive?&amp;quot;. There was
only one way to find out if this was usable or not...&lt;/p&gt;
&lt;p&gt;It took something like 30 minutes for the flash drive to be formatted and most
of the internal storage data to be moved to it. But at the end of that I had
successfully extended my storage by 128GB. And it felt... really sluggish. The
whole system would stutter all the time. Turns out that the USB flash drive
speed is a lot slower than the internal storage. And now that the system
depended on those transfers it was really noticeable.&lt;/p&gt;
&lt;p&gt;But maybe I could live with a slower phone in exchange for more storage space,
right? What about the big question: was &lt;em&gt;does&lt;/em&gt; happen when I remove the USB
drive? This is really important to know given that I'd need to remove it at
least to charge the phone every day or so. And the answer is: the phone doesn't
like it all. The system reboots 😝.&lt;/p&gt;
&lt;p&gt;I spent almost three days like this: With a phone with a giant 128GB storage
space, but that was laggy and would reboot every time I needed to recharge it
(which also happened more often as it turns out that the flash drive drains
quite a bit of battery while plugged in).&lt;/p&gt;
&lt;p&gt;At the end of those three days I had had enough of it so I reformated the flash
drive as external storage and went back to the previous setup. It was an
interesting (although frustrating) experiment.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="app-recommendations"&gt;
&lt;h2&gt;App recommendations&lt;/h2&gt;
&lt;p&gt;App-wise, I try as much as possible to use apps from the &lt;a class="reference external" href="https://f-droid.org/"&gt;F-Droid&lt;/a&gt; store, but
there are a handful of apps that I still need to use from the PlayStore.
Luckily, there's the builtin app AuroraStore for that, which at least provides a
privacy-friendly version of the PlayStore.&lt;/p&gt;
&lt;p&gt;For the apps that I have installed from F-Droid, here are the ones that I really
like:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;a class="reference external" href="https://f-droid.org/en/packages/de.danoeh.antennapod"&gt;AntennaPod&lt;/a&gt;: Podcast player.&lt;/li&gt;
&lt;li&gt;&lt;a class="reference external" href="https://f-droid.org/en/packages/com.google.android.diskusage"&gt;DiskUsage&lt;/a&gt;: Storage space usage viewer.&lt;/li&gt;
&lt;li&gt;&lt;a class="reference external" href="https://f-droid.org/en/packages/eu.depau.etchdroid"&gt;EtchDroid&lt;/a&gt;: USB image creator. I've covered it in the &lt;a class="reference external" href="/post/resurrecting-a-computer-on-the-go.html"&gt;"Resurrecting a computer on the go" post&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;&lt;a class="reference external" href="https://f-droid.org/en/packages/ml.docilealligator.infinityforreddit"&gt;Infinity&lt;/a&gt;: Reddit viewer.&lt;/li&gt;
&lt;li&gt;&lt;a class="reference external" href="https://f-droid.org/en/packages/com.kunzisoft.keepass.libre"&gt;KeePassDX&lt;/a&gt;: Password manager.&lt;/li&gt;
&lt;li&gt;&lt;a class="reference external" href="https://f-droid.org/en/packages/net.mullvad.mullvadvpn"&gt;Mullvad&lt;/a&gt;: VPN client.&lt;/li&gt;
&lt;li&gt;&lt;a class="reference external" href="https://f-droid.org/en/packages/org.schabi.newpipe"&gt;NewPipe&lt;/a&gt;: YouTube viewer.&lt;/li&gt;
&lt;li&gt;&lt;a class="reference external" href="https://f-droid.org/en/packages/net.osmand.plus/"&gt;OsmAnd~&lt;/a&gt;: Offline map viewer based on OpenStreetMap.&lt;/li&gt;
&lt;li&gt;&lt;a class="reference external" href="https://f-droid.org/en/packages/com.spicychair.weather"&gt;Pluvia&lt;/a&gt;: Weather forecast viewer.&lt;/li&gt;
&lt;li&gt;&lt;a class="reference external" href="https://f-droid.org/en/packages/name.boyle.chris.sgtpuzzles/"&gt;Puzzles&lt;/a&gt;: A collection of good puzzle games.&lt;/li&gt;
&lt;li&gt;&lt;a class="reference external" href="https://f-droid.org/en/packages/com.nutomic.syncthingandroid"&gt;Syncthing&lt;/a&gt;: File synchronizer.&lt;/li&gt;
&lt;li&gt;&lt;a class="reference external" href="https://f-droid.org/en/packages/com.termux"&gt;Termux&lt;/a&gt;: Terminal.&lt;/li&gt;
&lt;li&gt;&lt;a class="reference external" href="https://f-droid.org/en/packages/com.poupa.vinylmusicplayer"&gt;Vinyl&lt;/a&gt;: Music Player.&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class="section" id="conclusion"&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;I don't know how long I'll go on using this phone, but I'm surely not going back
to Android. I'll either stay with LineageOS or maybe take the plunge to
PostmarketOS, if the user experience is in the JustWorksTM level by then (or not
if I feel like I suddenly have a bunch of free time 😝). Either way I'm going to
make sure my next phone has a bit more storage space 🙂.&lt;/p&gt;
&lt;/div&gt;
</content><category term="2022"/><category term="nexus5x"/><category term="lineageos"/></entry><entry><title>Learning x86-64 assembly basics</title><link href="https://nfraprado.net/post/learning-x86-64-assembly-basics.html" rel="alternate"/><published>2022-02-25T00:00:00-03:00</published><updated>2022-02-25T00:00:00-03:00</updated><author><name>Nícolas F. R. A. Prado</name></author><id>tag:nfraprado.net,2022-02-25:/post/learning-x86-64-assembly-basics.html</id><summary type="html">&lt;p&gt;Recently I decided to learn assembly. I already had a reasonable understanding
of how it worked due to some classes that touched the subject in university,
however I never had the opportunity to really write assembly code.&lt;/p&gt;
&lt;p&gt;Since my everyday computer is an x86-64 machine, it made most sense to …&lt;/p&gt;</summary><content type="html">&lt;p&gt;Recently I decided to learn assembly. I already had a reasonable understanding
of how it worked due to some classes that touched the subject in university,
however I never had the opportunity to really write assembly code.&lt;/p&gt;
&lt;p&gt;Since my everyday computer is an x86-64 machine, it made most sense to learn
assembly for this architecture, so I could avoid the need for a VM. I started
with only the desire to get my hands dirty with assembly code, and not any
particular objective or project.&lt;/p&gt;
&lt;p&gt;At first I was alternating between trying things out and researching on the web
just to understand enough to get a bare minimum assembly file and commands that
would assemble it and run. Eventually I stumbled upon the book that would guide
me: &lt;a class="reference external" href="https://open.umn.edu/opentextbooks/textbooks/733"&gt;x86-64 Assembly Language Programming with Ubuntu&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;This book is free, recent and had the perfect scope for me: it's aimed at people
that already have a good grasp of programming, but are new to x86-64 assembly,
and it shows some theory and concepts, but there are plenty of exercises to
learn from practice.&lt;/p&gt;
&lt;p&gt;It was pretty fun to work through that book, and it worked well for me to create
some familiarity with x86-64 assembly. I'm sure there are still a bunch of
things to learn on the subject, since the book only gives a basis, but it was
enough to teach me some interesting things.&lt;/p&gt;
&lt;div class="section" id="signedness-and-two-s-complement"&gt;
&lt;h2&gt;Signedness and two's complement&lt;/h2&gt;
&lt;p&gt;The biggest lesson to me was a better understanding of signedness. I'm used to
seeing &lt;code class="docutils literal"&gt;int&lt;/code&gt; and &lt;code class="docutils literal"&gt;unsigned int&lt;/code&gt; in C, and to watch out for using the wrong
signedness, but it wasn't as clear to me how that worked at the assembly level.&lt;/p&gt;
&lt;p&gt;The first thing to have in mind, is that the type concept present in higher
level languages like C (like if a number is signed or not) is completely absent
in assembly. The computer memory stores only 0s and 1s, and it's up to you, the
programmer, to interpret what they mean: is &lt;code class="docutils literal"&gt;01011000&lt;/code&gt; the number 88, the
character &lt;code class="docutils literal"&gt;X&lt;/code&gt;, the &lt;code class="docutils literal"&gt;POP AX&lt;/code&gt; instruction? With only that single byte, you
can't even be sure of the size: maybe those are really 8 boolean flags in a
single byte, or part of a 4-byte signed number. Without context it's impossible
to tell.&lt;/p&gt;
&lt;p&gt;If the same representation can mean both a signed or unsigned number, depending
on the context, that means that when operating on those numbers, you as the
programmer have to use the right variant of the instruction to give that
context to the computer.&lt;/p&gt;
&lt;p&gt;While going through the book, the following arithmetic instructions were
presented for unsigned numbers:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;code class="docutils literal"&gt;add&lt;/code&gt; adds two numbers&lt;/li&gt;
&lt;li&gt;&lt;code class="docutils literal"&gt;sub&lt;/code&gt; subtracts two numbers&lt;/li&gt;
&lt;li&gt;&lt;code class="docutils literal"&gt;mul&lt;/code&gt; multiplies two numbers&lt;/li&gt;
&lt;li&gt;&lt;code class="docutils literal"&gt;div&lt;/code&gt; divides two numbers&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;And the following instructions were shown for comparison between unsigned
numbers:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;code class="docutils literal"&gt;ja&lt;/code&gt; compares two numbers and jumps if the first one is above the second&lt;/li&gt;
&lt;li&gt;&lt;code class="docutils literal"&gt;jb&lt;/code&gt; compares two numbers and jumps if the first one is below the second&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;And sure enough, shortly after, the signed variants of those instructions were
also shown:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;code class="docutils literal"&gt;imul&lt;/code&gt; is &lt;code class="docutils literal"&gt;mul&lt;/code&gt;'s signed variant&lt;/li&gt;
&lt;li&gt;&lt;code class="docutils literal"&gt;idiv&lt;/code&gt; is &lt;code class="docutils literal"&gt;div&lt;/code&gt;'s signed variant&lt;/li&gt;
&lt;li&gt;&lt;code class="docutils literal"&gt;jg&lt;/code&gt; is &lt;code class="docutils literal"&gt;ja&lt;/code&gt;'s signed variant&lt;/li&gt;
&lt;li&gt;&lt;code class="docutils literal"&gt;jl&lt;/code&gt; is &lt;code class="docutils literal"&gt;jb&lt;/code&gt;'s signed variant&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;But wait, what about &lt;code class="docutils literal"&gt;iadd&lt;/code&gt; and &lt;code class="docutils literal"&gt;isub&lt;/code&gt;? That's the thing, the way x86-64
represents negative numbers is through the use of the &lt;a class="reference external" href="https://en.wikipedia.org/wiki/Two%27s_complement"&gt;two's complement&lt;/a&gt;
system, which has the useful property of allowing addition and subtraction to be
done in the exactly same manner for both signed and unsigned values.&lt;/p&gt;
&lt;p&gt;This means that there's only one way to add, independently of the signedness,
and it's using &lt;code class="docutils literal"&gt;add&lt;/code&gt;. There's no &lt;code class="docutils literal"&gt;iadd&lt;/code&gt;. Likewise for subtraction.&lt;/p&gt;
&lt;p&gt;So the interesting conclusion is that for addition and subtraction it doesn't
matter if you use &lt;code class="docutils literal"&gt;unsigned int&lt;/code&gt; or &lt;code class="docutils literal"&gt;int&lt;/code&gt; for the variables in C. The
&lt;code class="docutils literal"&gt;unsigned&lt;/code&gt; keyword is there for you to tell the compiler to use the right
variant of the instruction in the generated assembly, which is required when
you're comparing numbers (&lt;code class="docutils literal"&gt;ja&lt;/code&gt; vs &lt;code class="docutils literal"&gt;jg&lt;/code&gt;, &lt;code class="docutils literal"&gt;jb&lt;/code&gt; vs &lt;code class="docutils literal"&gt;jl&lt;/code&gt;), multiplying
(&lt;code class="docutils literal"&gt;mul&lt;/code&gt; vs &lt;code class="docutils literal"&gt;imul&lt;/code&gt;) or dividing (&lt;code class="docutils literal"&gt;div&lt;/code&gt; vs &lt;code class="docutils literal"&gt;idiv&lt;/code&gt;). But thanks to two's
complement, in addition and subtraction there's no way to get it wrong 🙂.&lt;/p&gt;
&lt;p&gt;Side note: interestingly, while writing this post, I read on the Wikipedia page
that two's complement also works the same for multiplication, but only if you do
a sign extend of the two operands beforehand. Which makes me think that if the
&lt;code class="docutils literal"&gt;mul&lt;/code&gt; instruction always did the sign extend step, no &lt;code class="docutils literal"&gt;imul&lt;/code&gt; instruction
would be required as well, but that would probably increase complexity (and
cost) in the logic circuitry.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="other-interesting-lessons"&gt;
&lt;h2&gt;Other interesting lessons&lt;/h2&gt;
&lt;p&gt;The other thing that interested me the most was to realize that local variables
are nothing more than adding more space to the stack. And that this is done
simply by subtracting the stack register &lt;code class="docutils literal"&gt;rsp&lt;/code&gt; by the total number of bytes
needed for the variables at the start of a subroutine.&lt;/p&gt;
&lt;p&gt;Also interesting was to learn how there are &lt;a class="reference external" href="https://en.wikipedia.org/wiki/X86_calling_conventions#x86-64_calling_conventions"&gt;calling conventions&lt;/a&gt; to
standardize on:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;which registers are used to pass arguments to subroutines and in which order;&lt;/li&gt;
&lt;li&gt;which registers can be overwritten by a subroutine and which should be left
unchanged. When using the latter, its current value should first be pushed on
the stack so that it can be restored before returning.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;And what about the magic &lt;code class="docutils literal"&gt;main()&lt;/code&gt; function that the C compiler expects in
every C program? Assembly doesn't need compiling, so no need for that, but turns
out a different magic label is expected by the linker: &lt;code class="docutils literal"&gt;_start&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Some other things that were interesting to do in assembly:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;Making syscalls&lt;/li&gt;
&lt;li&gt;Exploiting a stack buffer overflow&lt;/li&gt;
&lt;li&gt;Interacting assembly code with C code, and vice versa.&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class="section" id="lack-of-a-good-gui"&gt;
&lt;h2&gt;Lack of a good GUI&lt;/h2&gt;
&lt;p&gt;One thing I missed was a good GUI application when debugging the assembly
programs. It would have been really helpful to have one that showed the values
of expressions in tooltips when hovering, that was able to follow labels when
clicking, and so on.&lt;/p&gt;
&lt;p&gt;The book recommends using DDD, which is a GUI, but it felt clunky and really
outdated. I went for using GDB together with the &lt;a class="reference external" href="https://github.com/longld/peda"&gt;peda&lt;/a&gt; plugin, and that worked
reasonably well, but being a CLI, every inspection required divining the correct
command, so it took more time to get oriented.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="conclusion"&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;This was a great experience and I hope to get back to it and further my
knowledge past the &amp;quot;basic&amp;quot; level for x86-64 sometime in the future. Seeing
what's happening at the assembly level really helps better understand the higher
level languages, and value the way they hide complexities below!&lt;/p&gt;
&lt;p&gt;I've uploaded the code I wrote for all the book's exercises &lt;a class="reference external" href="https://codeberg.org/nfraprado/x86-64-book-exercises"&gt;to this
repository&lt;/a&gt;. I don't expect it to be useful to anyone since it's simple stuff,
but it's there either way.&lt;/p&gt;
&lt;p&gt;The only exercise that I couldn't actually finish was the last one. There's very
little information on the book about how to do it, and during research of the
topic online I eventually got demotivated and started learning about other
subjects instead. Maybe one day I'll give it another try. If you do know how to
do it, &lt;a class="reference external" href="/pages/about.html"&gt;get in touch&lt;/a&gt;! 🙂&lt;/p&gt;
&lt;p&gt;And even though I couldn't finish that last exercise, while researching about it
I ended up learning about how to use the &lt;code class="docutils literal"&gt;asm&lt;/code&gt; syntax for GCC &lt;a class="reference external" href="https://www.felixcloutier.com/documents/gcc-asm.html"&gt;through this
guide&lt;/a&gt;, to embed assembly in a C file, and also about the &lt;a class="reference external" href="https://godbolt.org/"&gt;Compiler
Explorer&lt;/a&gt; which seems a great way to learn about assembly and C by just seeing
what assembly is generated from a given C code, so I'm calling this a win!&lt;/p&gt;
&lt;/div&gt;
</content><category term="2022"/><category term="assembly"/><category term="x86-64"/></entry><entry><title>Making internal linking in pelican effortless</title><link href="https://nfraprado.net/post/making-internal-linking-in-pelican-effortless.html" rel="alternate"/><published>2022-01-20T00:00:00-03:00</published><updated>2022-01-20T00:00:00-03:00</updated><author><name>Nícolas F. R. A. Prado</name></author><id>tag:nfraprado.net,2022-01-20:/post/making-internal-linking-in-pelican-effortless.html</id><summary type="html">&lt;p&gt;One interesting feature of &lt;a class="reference external" href="https://github.com/getpelican/pelican"&gt;Pelican&lt;/a&gt;, the static site generator I use for this
blog, is the internal link expansion syntax with &lt;code class="docutils literal"&gt;{}&lt;/code&gt;. It is documented
&lt;a class="reference external" href="https://docs.getpelican.com/en/latest/content.html?highlight=static#linking-to-internal-content"&gt;here&lt;/a&gt;. Some examples are &lt;code class="docutils literal"&gt;{filename}&lt;/code&gt;, &lt;code class="docutils literal"&gt;{static}&lt;/code&gt; and &lt;code class="docutils literal"&gt;{author}&lt;/code&gt;. The
purpose of the syntax is to have shorter and easier aliases to link to internal
content in …&lt;/p&gt;</summary><content type="html">&lt;p&gt;One interesting feature of &lt;a class="reference external" href="https://github.com/getpelican/pelican"&gt;Pelican&lt;/a&gt;, the static site generator I use for this
blog, is the internal link expansion syntax with &lt;code class="docutils literal"&gt;{}&lt;/code&gt;. It is documented
&lt;a class="reference external" href="https://docs.getpelican.com/en/latest/content.html?highlight=static#linking-to-internal-content"&gt;here&lt;/a&gt;. Some examples are &lt;code class="docutils literal"&gt;{filename}&lt;/code&gt;, &lt;code class="docutils literal"&gt;{static}&lt;/code&gt; and &lt;code class="docutils literal"&gt;{author}&lt;/code&gt;. The
purpose of the syntax is to have shorter and easier aliases to link to internal
content in the blog. For example, &lt;code class="docutils literal"&gt;{filename}&lt;/code&gt; can be used to link to other
files, like posts.&lt;/p&gt;
&lt;p&gt;The idea is good, but to my needs it fell short. What I really need internal
link expansions for in my blog are images, posts and code.&lt;/p&gt;
&lt;div class="section" id="images"&gt;
&lt;h2&gt;Images&lt;/h2&gt;
&lt;p&gt;The idea for images is really simple. I write my posts in &lt;a class="reference external" href="https://docutils.sourceforge.io/rst.html"&gt;rst&lt;/a&gt;, and this is how
an image is included in this format:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="p"&gt;..&lt;/span&gt; &lt;span class="ow"&gt;image&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt; path/to/image.jpg
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;The way I structure the files in the blog (which you can see in &lt;a class="reference external" href="https://codeberg.org/nfraprado/blog"&gt;its
repository&lt;/a&gt;), is that inside the &lt;code class="docutils literal"&gt;content&lt;/code&gt; folder where all content is, the
sources for the posts can be found inside &lt;code class="docutils literal"&gt;articles/&amp;lt;year&amp;gt;/&lt;/code&gt;, and the images
can be found at &lt;code class="docutils literal"&gt;images/&amp;lt;post_id&amp;gt;/&lt;/code&gt;, where &lt;code class="docutils literal"&gt;&amp;lt;post_id&amp;gt;&lt;/code&gt; is a string that
identifies the post in which the image appears (it is the &lt;code class="docutils literal"&gt;trans_id&lt;/code&gt; property
of the post, derived by its filename and used to associate translations of the
same post, but I'm calling it &lt;code class="docutils literal"&gt;post_id&lt;/code&gt; here since it makes more sense in this
context).&lt;/p&gt;
&lt;p&gt;From this structure, if I use a relative path from a post to one of its images,
it'd have to be something like &lt;code class="docutils literal"&gt;../../images/&amp;lt;post_id&amp;gt;/image_name.jpg&lt;/code&gt;. The
&lt;code class="docutils literal"&gt;{static}&lt;/code&gt; expansion can be used here to simplify it a bit:
&lt;code class="docutils literal"&gt;{static}/images/&amp;lt;post_id&amp;gt;/image_name.jpg&lt;/code&gt;. We can do better than this though
🙂.&lt;/p&gt;
&lt;p&gt;It'd be way better if this path could be really shortened. All images are inside
the &lt;code class="docutils literal"&gt;images/&lt;/code&gt; folder, so that should be implied. Heck, while we're at it,
might as well make the &lt;code class="docutils literal"&gt;&amp;lt;post_id&amp;gt;/&lt;/code&gt; part be derived from the current post's
id. That would make it perfect, since the only information left is the image
name, which is the unique thing about the link.&lt;/p&gt;
&lt;p&gt;The first step in implementing this custom logic was adding the &lt;a class="reference external" href="https://github.com/getpelican/pelican-plugins/blob/master/linker/linker.py"&gt;linker plugin&lt;/a&gt;
to pelican. It allows you to implement your own &lt;code class="docutils literal"&gt;{}&lt;/code&gt; link expansions through
python classes.&lt;/p&gt;
&lt;p&gt;And the second step was implementing a &lt;code class="docutils literal"&gt;{image}&lt;/code&gt; expansion through &lt;a class="reference external" href="https://codeberg.org/nfraprado/blog/commit/fb4238f841f9a242bdf5f20badd5bb50ccb221b4"&gt;this
commit&lt;/a&gt;. &lt;a class="reference external" href="https://codeberg.org/nfraprado/blog/commit/dbaaace5ba0e00e2f378ba8c8d7e80b6c36d4326"&gt;This is the commit&lt;/a&gt; where I updated all image links to use
&lt;code class="docutils literal"&gt;{image}&lt;/code&gt;. Feel the joy! It's so much neater 🙂.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="posts"&gt;
&lt;h2&gt;Posts&lt;/h2&gt;
&lt;p&gt;Linking to posts is a little trickier, but not too bad. The idea is, I somewhat
frequently want to link to another post I wrote previously on the blog. Using
the &lt;code class="docutils literal"&gt;{filename}&lt;/code&gt; expansion, it's not too bad:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;I&amp;#39;ve shown that &lt;span class="s"&gt;`in a previous post`__&lt;/span&gt;.

&lt;span class="p"&gt;..&lt;/span&gt; &lt;span class="nt"&gt;__:&lt;/span&gt; {filename}08-task-context-en.rst
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;But it could certainly be better. The &lt;code class="docutils literal"&gt;{filename}&lt;/code&gt; expansion is relative to
the current file, so if I'm referencing a post from the same year, it's like in
the example above, but referencing one from a different year requires an
additional &lt;code class="docutils literal"&gt;../&amp;lt;year&amp;gt;/&lt;/code&gt; in the path. Also, I shouldn't need to write the whole
filename. Ideally I'd only need to type what's unique to the post, that is, its
&lt;code class="docutils literal"&gt;post_id&lt;/code&gt;. Yes, even the language suffix (&lt;code class="docutils literal"&gt;-en&lt;/code&gt; or &lt;code class="docutils literal"&gt;-br&lt;/code&gt;) can be omitted,
since I can derive it from the current post's language.&lt;/p&gt;
&lt;p&gt;So far so good, it could be done with a bit more logic on top of what was done
for &lt;code class="docutils literal"&gt;{image}&lt;/code&gt;. But since I'm already improving things, I'd also like to take
this chance to better standardize the text I use in the links. Sure, using a
text like &amp;quot;in a previous post&amp;quot; blends well with the surrounding text, but it
isn't immediately obvious that the link is to another post in my blog.&lt;/p&gt;
&lt;p&gt;So the idea is to have a link expansion that not only maps to the right path to
the post, but also automatically sets its text to have the post title. One final
touch is that when setting the text I want it to also take into account the
language of the post: if it's in English, the text should be in the format
'&amp;quot;Title of the post&amp;quot; post' and if it's in Portuguese, 'artigo &amp;quot;Title of the
post&amp;quot;'.&lt;/p&gt;
&lt;p&gt;Updating the link's text is not something the linker plugin can do natively, so
I first needed to extend it to enable that &lt;a class="reference external" href="https://codeberg.org/nfraprado/blog/commit/e6a0ea78e2898b7e7004aff9aa17e6be354dbdd8"&gt;in this commit&lt;/a&gt;. With the basic
mechanism in place, I actually implemented the new &lt;code class="docutils literal"&gt;{article}&lt;/code&gt; expansion &lt;a class="reference external" href="https://codeberg.org/nfraprado/blog/commit/ff4f7fc0cc4a418a19574e41510e8f28a62c5e31"&gt;in
this commit&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;All that work pays off, as now I can show you how it looks by pointing to the
very first post I wrote on this blog through a simple &lt;code class="docutils literal"&gt;{article}tasklist&lt;/code&gt;:
&lt;a class="reference external" href="/post/creating-movie-and-game-lists-using-taskwarrior.html"&gt;"Creating movie and game lists using Taskwarrior" post&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Of course I also changed all references to blog posts to this new amazing
expansion &lt;a class="reference external" href="https://codeberg.org/nfraprado/blog/commit/c83e1cc8a8484777776a7b6fd70f758a84c351d1"&gt;in this commit&lt;/a&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="code"&gt;
&lt;h2&gt;Code&lt;/h2&gt;
&lt;p&gt;This is where it gets messy... You see, it's quite common for my blog posts,
being technical, to have code blocks amidst the text. The way I include the code
is by using the &lt;code class="docutils literal"&gt;include&lt;/code&gt; rst directive and giving it the path to a separate
file containing the code.&lt;/p&gt;
&lt;p&gt;The issue is, unlike the &lt;code class="docutils literal"&gt;image&lt;/code&gt; rst directive and rst links whose target
appears in the final HTML, which makes it simple to edit them inside pelican,
the &lt;code class="docutils literal"&gt;include&lt;/code&gt; directive and its path are processed by the rst reader at an
earlier stage. This means that the normal link tweaking methods in pelican can't
be used here (like the linker plugin).&lt;/p&gt;
&lt;p&gt;I could put the code inline instead of using the &lt;code class="docutils literal"&gt;include&lt;/code&gt; directive and avoid
this problem altogether, but when the code is more than a few lines, I feel like
it would pollute the post source file too much to have it inline.&lt;/p&gt;
&lt;p&gt;Well, if you've read the &lt;a class="reference external" href="/post/blog-customizations.html"&gt;"Blog customizations" post&lt;/a&gt; you might remember that I
already have a custom rst reader. So in order to implement a link expansion for
code, I had to extend this reader to substitute any &lt;code class="docutils literal"&gt;{code}&lt;/code&gt; occurrence inside
an &lt;code class="docutils literal"&gt;include&lt;/code&gt; directive with the code file's path. That's what I did &lt;a class="reference external" href="https://codeberg.org/nfraprado/blog/commit/8d8f03b5fd5448d1c880f782555f6f4e1717a36c"&gt;in this
commit&lt;/a&gt;, by copying the code from pelican's rst reader and making a few
changes.&lt;/p&gt;
&lt;p&gt;It's worth saying that this kind of customization I'm doing (and was already
doing) by overriding the rst reader is not really stable. If there are changes
in pelican's rst reader, I might need to either stop updating pelican or
reimplement their changes in my own custom reader. But since there isn't really
a less intrusive way of implementing this, I'm willing to take the risk.&lt;/p&gt;
&lt;p&gt;Despite the ugly changes, in the end it's all worth it when you look at the
improvements in the post sources. &lt;a class="reference external" href="https://codeberg.org/nfraprado/blog/commit/3007137d04992b6720cd88691b25ec7bb5a6995a"&gt;This is the commit&lt;/a&gt; where I updated them to
use the new &lt;code class="docutils literal"&gt;{code}&lt;/code&gt; expansion. I dare you to say it wasn't worth it (please
don't say it).&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="conclusion"&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;And here we are. Took some work, but after these changes, internal linking on
my blog is effortless! Anything that makes writing blog posts easier is well
worth it to me.&lt;/p&gt;
&lt;/div&gt;
</content><category term="2022"/><category term="blog"/></entry><entry><title>The menus in my system</title><link href="https://nfraprado.net/post/the-menus-in-my-system.html" rel="alternate"/><published>2021-12-28T00:00:00-03:00</published><updated>2021-12-28T00:00:00-03:00</updated><author><name>Nícolas F. R. A. Prado</name></author><id>tag:nfraprado.net,2021-12-28:/post/the-menus-in-my-system.html</id><summary type="html">&lt;p&gt;Another post going through stuff I set up for my desktop environment a couple
years ago and that have used ever since 🙂. This time I'll show the menus I've
created using &lt;a class="reference external" href="https://github.com/davatorium/rofi"&gt;rofi&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;So, what is &lt;a class="reference external" href="https://github.com/davatorium/rofi"&gt;rofi&lt;/a&gt;? It's basically a program where you feed a list of options to
it, and …&lt;/p&gt;</summary><content type="html">&lt;p&gt;Another post going through stuff I set up for my desktop environment a couple
years ago and that have used ever since 🙂. This time I'll show the menus I've
created using &lt;a class="reference external" href="https://github.com/davatorium/rofi"&gt;rofi&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;So, what is &lt;a class="reference external" href="https://github.com/davatorium/rofi"&gt;rofi&lt;/a&gt;? It's basically a program where you feed a list of options to
it, and it shows a window where the user can filter and choose an option. So
simply put it is a simple universal selection menu.&lt;/p&gt;
&lt;p&gt;Rofi also has some default menus, like one to launch applications and another to
switch windows, which are probably its most common use. I also use these menus,
but since they aren't something I customized myself, I won't talk about them.&lt;/p&gt;
&lt;div class="section" id="my-menus"&gt;
&lt;h2&gt;My menus&lt;/h2&gt;
&lt;p&gt;The menus I created with rofi are: power, screenshot, unicode and music.&lt;/p&gt;
&lt;div class="section" id="power"&gt;
&lt;h3&gt;Power&lt;/h3&gt;
&lt;img alt="{image}/power.png" src="/images/rofi/power.png" /&gt;
&lt;p&gt;This is probably the most important menu to have when your desktop environment
doesn't have its own power menu (&lt;em&gt;e.g.&lt;/em&gt; in my case, I use sway which is just a
window manager and doesn't have any menu of its own). This menu is bound to
&lt;code class="docutils literal"&gt;Super+Shift+P&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Each option is probably self-explanatory. They're sorted from least &amp;quot;damaging&amp;quot;
on top to most &amp;quot;damaging&amp;quot; on the bottom, so that I don't accidentally shutdown
and lose my work when I just want to lock the screen, for example, since there's
no confirmation pop-up.&lt;/p&gt;
&lt;p&gt;Since &lt;code class="docutils literal"&gt;lock&lt;/code&gt; is the first option, simply opening the menu and pressing
&lt;code class="docutils literal"&gt;Enter&lt;/code&gt; already locks the screen. The other option I use the most is
&lt;code class="docutils literal"&gt;hibernate&lt;/code&gt;, which I just need to press &lt;code class="docutils literal"&gt;h&lt;/code&gt; for it to get selected, followed
by &lt;code class="docutils literal"&gt;Enter&lt;/code&gt; to hibernate. In the rare cases I need to really shutdown I use
&lt;code class="docutils literal"&gt;w&lt;/code&gt;, and for reboot, I use &lt;code class="docutils literal"&gt;re&lt;/code&gt;. Of course I can always cycle through the
options using &lt;code class="docutils literal"&gt;Ctrl+N&lt;/code&gt; and &lt;code class="docutils literal"&gt;Ctrl+P&lt;/code&gt; as well, but the letters are usually
faster.&lt;/p&gt;
&lt;p&gt;The reason there's a lot of space between each icon and the text is because each
icon has a different width, so in order to have the text of all entries aligned,
I had to put a tab character after each icon. The same is done in the other
menus.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="screenshot"&gt;
&lt;h3&gt;Screenshot&lt;/h3&gt;
&lt;img alt="{image}/screenshot.png" src="/images/rofi/screenshot.png" /&gt;
&lt;p&gt;The screenshot menu is bound to &lt;code class="docutils literal"&gt;Super+Shift+S&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;The &lt;code class="docutils literal"&gt;screen&lt;/code&gt; option takes a screenshot of the whole screen (including external
monitors). It calls &lt;a class="reference external" href="https://github.com/emersion/grim"&gt;grim&lt;/a&gt; under the hood to take the screenshots.&lt;/p&gt;
&lt;p&gt;&lt;code class="docutils literal"&gt;region&lt;/code&gt; invokes a cursor that allows me to select the rectangular shape that
will be screenshot. This is done by calling &lt;a class="reference external" href="https://github.com/emersion/slurp"&gt;slurp&lt;/a&gt;, which gets the selection
from the user, and feeding the resulting coordinates to &lt;code class="docutils literal"&gt;grim&lt;/code&gt; through its
&lt;code class="docutils literal"&gt;-g&lt;/code&gt; flag.&lt;/p&gt;
&lt;p&gt;&lt;code class="docutils literal"&gt;window&lt;/code&gt; also invokes a cursor, but it is used to select a single window to
screenshot. This is done by parsing the dimensions of the windows from sway, and
then feeding those as the options to &lt;code class="docutils literal"&gt;slurp&lt;/code&gt; which will then only allow one of
those rectangles to be selected. The command to do that, available in slurp's
README, is:&lt;/p&gt;
&lt;pre class="code literal-block"&gt;
swaymsg -t get_tree | jq -r '.. | select(.pid? and .visible?) | .rect | \&amp;quot;\(.x),\(.y) \(.width)x\(.height)\&amp;quot;' | slurp
&lt;/pre&gt;
&lt;p&gt;&lt;code class="docutils literal"&gt;colorpicker&lt;/code&gt; invokes a cursor as well, but doesn't take any screenshot,
instead the pixel that is clicked will have its RGB value copied to the
clipboard.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="unicode"&gt;
&lt;h3&gt;Unicode&lt;/h3&gt;
&lt;img alt="{image}/unicode.png" src="/images/rofi/unicode.png" /&gt;
&lt;p&gt;The unicode menu is bound to &lt;code class="docutils literal"&gt;Super+Shift+U&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;The purpose of this menu is to make it easier to access characters that I don't
normally have mapped in my keyboard. It shows each character followed by its
name. Since there are so many characters, I use two columns on rofi so I can see
more at once.&lt;/p&gt;
&lt;p&gt;To use this menu I write the name of the character I want and, after it gets
selected, I press &lt;code class="docutils literal"&gt;Enter&lt;/code&gt; to copy the character to the clipboard. I can then
paste it wherever I need it.&lt;/p&gt;
&lt;p&gt;One example usage is to get the ordinal characters used in Portuguese: ª and º
(called Feminine and Masculine Ordinal Indicator, respectively). These are
mapped in the Portuguese keyboard layout, but to me it's easier to search for
them in the menu when I want them than finding them in the keyboard.&lt;/p&gt;
&lt;p&gt;This menu is generated from python using the &lt;code class="docutils literal"&gt;unicodedata.name()&lt;/code&gt; function to
get the Unicode name for each character, and &lt;code class="docutils literal"&gt;chr()&lt;/code&gt; to get the actual
character. &lt;code class="docutils literal"&gt;wl-copy&lt;/code&gt; is used to copy the character to the clipboard.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="music"&gt;
&lt;h3&gt;Music&lt;/h3&gt;
&lt;img alt="{image}/music.png" src="/images/rofi/music.png" /&gt;
&lt;p&gt;The music menu is bound to &lt;code class="docutils literal"&gt;Super+Shift+M&lt;/code&gt; and it's my favorite! Each option
opens its own sub-menu. It has all that I need to always be able to quickly play
the music I want.&lt;/p&gt;
&lt;p&gt;One nice detail is that if a song is already playing, whatever you choose will
only play after the current song finishes. Otherwise, if no song is currently
playing, it starts playing immediately. This way the current song is never
stopped midway which is great.&lt;/p&gt;
&lt;p&gt;Also, differently from the other menus, the music sub-menus allow (when it makes
sense) selecting multiple options by using &lt;code class="docutils literal"&gt;Shift+Enter&lt;/code&gt; instead of just plain
&lt;code class="docutils literal"&gt;Enter&lt;/code&gt; when selecting, although I seldom use this.&lt;/p&gt;
&lt;p&gt;Let's see each one of the sub-menus.&lt;/p&gt;
&lt;div class="section" id="playlist"&gt;
&lt;h4&gt;Playlist&lt;/h4&gt;
&lt;img alt="{image}/music-playlist.png" src="/images/rofi/music-playlist.png" /&gt;
&lt;p&gt;The playlist menu allows me to start playing any of my playlists. Randomization
is automatically turned on when I select a playlist, so that the order of the
songs in the playlist aren't always the same.&lt;/p&gt;
&lt;p&gt;The playlist I listen to the most, &lt;code class="docutils literal"&gt;Saved&lt;/code&gt;, is the first one on purpose. This
way when I just want to start listening to &lt;em&gt;something&lt;/em&gt;, it's as easy as
&lt;code class="docutils literal"&gt;Super+Shift+M&lt;/code&gt;, &lt;code class="docutils literal"&gt;Enter&lt;/code&gt;, &lt;code class="docutils literal"&gt;Enter&lt;/code&gt;. Listening to background music when I
have to concentrate is also pretty easy: &lt;code class="docutils literal"&gt;Super+Shift+M&lt;/code&gt;, &lt;code class="docutils literal"&gt;Enter&lt;/code&gt;,
&lt;code class="docutils literal"&gt;Ctrl+N&lt;/code&gt;, &lt;code class="docutils literal"&gt;Enter&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;In case you're curious about how the playlists themselves are created, I've
already talked about that on the &lt;a class="reference external" href="/post/playlist-generation-with-mpd.html"&gt;"Playlist generation with MPD" post&lt;/a&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="song"&gt;
&lt;h4&gt;Song&lt;/h4&gt;
&lt;img alt="{image}/music-song.png" src="/images/rofi/music-song.png" /&gt;
&lt;p&gt;The song menu is for when I want to listen to a particular song. Each entry
shows the song name, artist and album. rofi doesn't support showing multiple
strings in the same entry like this by itself, so to achieve this I need to
construct a string with the same width as the rofi window, with each field
occupying an equal amount.&lt;/p&gt;
&lt;p&gt;To have a more or less predictable width I fix rofi's width to be
character-based by putting &lt;code class="docutils literal"&gt;width: -100;&lt;/code&gt; in its config. I then make this
value available inside the python script for this menu by simply reading the
value from this config (not pretty, but works). I then calculate the width each
field should have and add spacing and/or truncate each of the fields
individually as needed for them to keep the right width.&lt;/p&gt;
&lt;p&gt;Since the font I use in rofi isn't monospaced, the width I set in the config is
just an estimation and not precise in any way. So there can be a bit of extra
slack, but it works well enough.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="queue"&gt;
&lt;h4&gt;Queue&lt;/h4&gt;
&lt;img alt="{image}/music-queue.png" src="/images/rofi/music-queue.png" /&gt;
&lt;p&gt;The queue menu shows the current queue of songs that are playing. I use it
whenever I want to see which songs will play next or play a different song from
the current queue.&lt;/p&gt;
&lt;p&gt;When this menu is opened the cursor starts on the currently playing song.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="album"&gt;
&lt;h4&gt;Album&lt;/h4&gt;
&lt;img alt="{image}/music-album.png" src="/images/rofi/music-album.png" /&gt;
&lt;p&gt;The album menu lets me choose an album to play. One important distinction is
that playing an album turns randomization off, so that the songs in the album
are played in the right order, while the Playlist and Artist menus play with
randomization turned on.&lt;/p&gt;
&lt;p&gt;Each entry shows the album and artist name. On the front there's also a &lt;code class="docutils literal"&gt;%&lt;/code&gt; to
mark what I call &amp;quot;full albums&amp;quot;. The criterion is that if I like at least 80% of
the songs in an album, I mark it with a &lt;code class="docutils literal"&gt;%&lt;/code&gt;, and listen to it whole, even the
songs I didn't quite like. On the other hand when I like less than 80% of the
songs in an album, I just listen to the ones I liked.&lt;/p&gt;
&lt;p&gt;The idea behind the &amp;quot;full album&amp;quot; concept is that I think there's some value to
listening to an album as a whole, but I also don't think it makes sense to
listen to a whole album when you don't like most of it. So I have that 20% slack
so that albums don't need to be perfect before I'm willing to listen to them
wholly.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="artist"&gt;
&lt;h4&gt;Artist&lt;/h4&gt;
&lt;img alt="{image}/music-artist.png" src="/images/rofi/music-artist.png" /&gt;
&lt;p&gt;The artist menu is the one I use to play all musics and albums from a single
artist, in no particular order.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="current"&gt;
&lt;h4&gt;Current&lt;/h4&gt;
&lt;img alt="{image}/music-current.png" src="/images/rofi/music-current.png" /&gt;
&lt;p&gt;The current menu is a very new addition! I noticed that I'm sometimes listening
to some playlist and a song plays that makes me want to listen to its whole
album or to all songs from that artist. This menu allows me to do exactly that.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="options"&gt;
&lt;h4&gt;Options&lt;/h4&gt;
&lt;img alt="{image}/music-options.png" src="/images/rofi/music-options.png" /&gt;
&lt;p&gt;The options menu gives access to some extra functionalities. &lt;code class="docutils literal"&gt;update&lt;/code&gt; causes
MPD to update the music database, which is sometimes useful. &lt;code class="docutils literal"&gt;random&lt;/code&gt; toggles
the music randomization. I rarely use this since I have already configured my
menus to turn randomization on or off depending if it's an album or not.
&lt;code class="docutils literal"&gt;update playlists&lt;/code&gt; re-runs my playlists generation script so they get updated.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="other-considerations"&gt;
&lt;h2&gt;Other considerations&lt;/h2&gt;
&lt;p&gt;Those are my menus and what I wanted to share in this post, but it's worth
mentioning a few other points.&lt;/p&gt;
&lt;p&gt;First, there's another rofi menu that use, although it isn't one that I created
myself. It's called &lt;a class="reference external" href="https://github.com/fdw/rofimoji"&gt;rofimoji&lt;/a&gt; and allows you to very easily search for and copy
emojis.&lt;/p&gt;
&lt;p&gt;Second, it's worth mentioning that rofi officially only works on X11, so I
actually use the &lt;a class="reference external" href="https://aur.archlinux.org/packages/rofi-lbonn-wayland-git/"&gt;rofi wayland fork&lt;/a&gt;, as I've mentioned on the
&lt;a class="reference external" href="/post/moving-to-wayland.html"&gt;"Moving to Wayland" post&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;And finally, in order to more easily create rofi menus from my python scripts,
I've wrapped rofi in a python function with the following prototype:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;select&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;prompt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;multi&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;False&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[]):&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;where &lt;code class="docutils literal"&gt;prompt&lt;/code&gt; is the string that shows in the prompt, &lt;code class="docutils literal"&gt;options&lt;/code&gt; is the list
of options that can be chosen from, &lt;code class="docutils literal"&gt;multi&lt;/code&gt; is whether multiple options can be
selected or not, and &lt;code class="docutils literal"&gt;args&lt;/code&gt; allows passing additional arguments to rofi. The
return of the function is the option(s) that was/were selected (just the option
if a single one, and a list of the options if more than one). If the selection
was aborted an exception is raised. I considered using &lt;a class="reference external" href="https://github.com/bcbnz/python-rofi"&gt;python-rofi&lt;/a&gt; instead of
creating my own, but that module returns the index of the selected entry instead
of the entry itself, which I found would just make it more convoluted to use.
Since it was pretty easy to create my own module, I just went for it.&lt;/p&gt;
&lt;p&gt;That's it. I really like how easy it is to make menus using rofi from python 🙂.&lt;/p&gt;
&lt;/div&gt;
</content><category term="2021"/><category term="rofi"/><category term="desktop"/></entry><entry><title>The blocks in my status bar</title><link href="https://nfraprado.net/post/the-blocks-in-my-status-bar.html" rel="alternate"/><published>2021-11-26T00:00:00-03:00</published><updated>2021-11-26T00:00:00-03:00</updated><author><name>Nícolas F. R. A. Prado</name></author><id>tag:nfraprado.net,2021-11-26:/post/the-blocks-in-my-status-bar.html</id><summary type="html">&lt;p&gt;Five years ago when I moved to the i3 window manager, I started using its
status bar, the i3bar. It is text based, and it's up to you what gets shown
there. However it is not very modular: it's weird to combine different
information to be shown since everything has …&lt;/p&gt;</summary><content type="html">&lt;p&gt;Five years ago when I moved to the i3 window manager, I started using its
status bar, the i3bar. It is text based, and it's up to you what gets shown
there. However it is not very modular: it's weird to combine different
information to be shown since everything has to be joined in a single string
manually.&lt;/p&gt;
&lt;p&gt;About one year later I discovered a program to solve that issue: &lt;a class="reference external" href="https://github.com/vivien/i3blocks"&gt;i3blocks&lt;/a&gt;. The
way it works is that in its config you define the blocks that you want and what
script will be run for each one. The text output for each script is what will be
shown for that block in the status bar.&lt;/p&gt;
&lt;div class="section" id="my-blocks"&gt;
&lt;h2&gt;My blocks&lt;/h2&gt;
&lt;p&gt;I currently have 9 blocks in my status bar: time, schedule, task, battery,
keyboard, storage-root, storage-home, update and music.&lt;/p&gt;
&lt;div class="section" id="time"&gt;
&lt;h3&gt;Time&lt;/h3&gt;
&lt;img alt="{image}/time.png" src="/images/i3blocks/time.png" /&gt;
&lt;p&gt;Probably the most obvious block. I can't think of a status bar that doesn't show
the current time...&lt;/p&gt;
&lt;p&gt;This block shows the date (weekday, day, month and year) and time. Having the
time showing in bold is a detail I really like, but don't recall where I got the
idea from.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="schedule"&gt;
&lt;h3&gt;Schedule&lt;/h3&gt;
&lt;img alt="{image}/schedule.png" src="/images/i3blocks/schedule.png" /&gt;
&lt;p&gt;This block shows me the current schedule &lt;em&gt;i.e.&lt;/em&gt; what I should be doing right now.
As you can see I should be reading a book instead of writing this blog post
right now... But I have to hurry if I want to post this! 😝&lt;/p&gt;
&lt;p&gt;I have shown this block previously in the &lt;a class="reference external" href="/post/organization-beyond-taskwarrior.html"&gt;"Organization beyond Taskwarrior" post&lt;/a&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="task"&gt;
&lt;h3&gt;Task&lt;/h3&gt;
&lt;img alt="{image}/task.png" src="/images/i3blocks/task.png" /&gt;
&lt;p&gt;This block shows my current task context and the number of tasks I have
currently in my inbox, the number of stuck projects, and the number of tasks
with near due dates.&lt;/p&gt;
&lt;p&gt;When all three are zero, this block is hidden, although that is very rare. As
you can see, I haven't had enough time to sort the tasks in my inbox lately 😅.&lt;/p&gt;
&lt;p&gt;I also showed this block already in the &lt;a class="reference external" href="/post/organization-beyond-taskwarrior.html"&gt;"Organization beyond Taskwarrior" post&lt;/a&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="battery"&gt;
&lt;h3&gt;Battery&lt;/h3&gt;
&lt;img alt="{image}/battery.png" src="/images/i3blocks/battery.png" /&gt;
&lt;p&gt;This block shows the battery charge percentage. The icon reflects the current
charge (out of 4 possibilities), and when the battery is low the background
becomes red to catch my attention.&lt;/p&gt;
&lt;p&gt;When the battery is charging and at a high percentage, this block is hidden.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="keyboard"&gt;
&lt;h3&gt;Keyboard&lt;/h3&gt;
&lt;img alt="{image}/keyboard.png" src="/images/i3blocks/keyboard.png" /&gt;
&lt;p&gt;This block shows me the current keyboard layout. Since I only use two layouts,
pt-br (ABNT2) and en-us, and most of the time I use the english one, this block
only shows in red when I'm in the portuguese layout. When I'm in the english
one, it stays hidden.&lt;/p&gt;
&lt;p&gt;I introduced this block after too many times trying to use vim and having
forgotten that I was in the portuguese layout.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="storage"&gt;
&lt;h3&gt;Storage&lt;/h3&gt;
&lt;img alt="{image}/storage-root.png" src="/images/i3blocks/storage-root.png" /&gt;
&lt;img alt="{image}/storage-home.png" src="/images/i3blocks/storage-home.png" /&gt;
&lt;p&gt;These two blocks show the available and total storage spaces on my disks. The
first (computer icon) shows for the root partition, while the second one (home
icon) shows for the home partition. These blocks are almost always hidden,
unless the free space gets low, in which case they show up with the red
background.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Note&lt;/strong&gt;: The free space shown in the pictures wouldn't normally be considered
low, I just forced them to appear here.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="update"&gt;
&lt;h3&gt;Update&lt;/h3&gt;
&lt;img alt="{image}/update.png" src="/images/i3blocks/update.png" /&gt;
&lt;p&gt;This block shows the number of packages that can be updated. It only shows if
the number is high enough, otherwise the block stays hidden.&lt;/p&gt;
&lt;p&gt;I used to have this block turn the background red if the kernel package needed
to be updated, since I noticed that in Arch Linux updating the kernel doesn't
keep the modules for the previous version around. This means that updating the
kernel should be followed by a system reboot, otherwise some weird bugs could
happen due to the lack of required modules (&lt;em&gt;e.g.&lt;/em&gt; USBs not working). However after
I found out about the &lt;a class="reference external" href="https://archlinux.org/packages/community/any/kernel-modules-hook/"&gt;kernel-modules-hook&lt;/a&gt; package that solves this problem, I
removed this check and update the kernel without worrying.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="music"&gt;
&lt;h3&gt;Music&lt;/h3&gt;
&lt;img alt="{image}/music.png" src="/images/i3blocks/music.png" /&gt;
&lt;p&gt;This block shows me the current playing music. It shows the artist followed by the
track name, and the icon reflects the current state: playing or paused. When no
music is playing the block stays hidden.&lt;/p&gt;
&lt;p&gt;Since the artist or track name can get very long, when they get longer than a
fixed length, I omit the rest using &amp;quot;...&amp;quot;.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="old-block-cardapio-unicamp"&gt;
&lt;h3&gt;Old block: cardapio-unicamp&lt;/h3&gt;
&lt;img alt="{image}/cardapio-unicamp.png" src="/images/i3blocks/cardapio-unicamp.png" /&gt;
&lt;img alt="{image}/cardapio-unicamp-semana.png" src="/images/i3blocks/cardapio-unicamp-semana.png" /&gt;
&lt;p&gt;This is not a block that I currently use, but it was my favorite one so I want
to mention it. Its purpose was to inform me of the meal that was being served at
the university's restaurant, so I could decide if I wanted to eat there or
somewhere else.&lt;/p&gt;
&lt;p&gt;There were two iterations of it. In the beginning, it simply showed the meal
description for the next meal. After some time I changed it to instead show the
quality of the meals for the whole week. I did this by matching the meal
description with words I considered either good or bad. The end result was ten
squares, each day separated by a &lt;code class="docutils literal"&gt;|&lt;/code&gt; and with the first square for lunch and
the second for dinner. The square colors showed the quality: white for normal,
green for good and red (or magenta in this old color scheme) for bad.&lt;/p&gt;
&lt;p&gt;The meal descriptions were queried using a short python program of my own,
called &lt;a class="reference external" href="https://codeberg.org/nfraprado/cardapio-unicamp"&gt;cardapio-unicamp&lt;/a&gt;. The script to get the meal quality for the whole week
is &lt;a class="reference external" href="https://codeberg.org/nfraprado/cardapio-unicamp/src/branch/master/exemplos/cardapio_ru_semana.py"&gt;also available in that repository&lt;/a&gt;. The conversion from the &lt;code class="docutils literal"&gt;|&lt;/code&gt;, &lt;code class="docutils literal"&gt;+&lt;/code&gt;
and &lt;code class="docutils literal"&gt;-&lt;/code&gt; to the colored squares I don't have published, but it was a couple of
simple (yet ugly) lines of bash, so I'm sure you can do something better if you
want to have that 🙂.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="improvements"&gt;
&lt;h2&gt;Improvements&lt;/h2&gt;
&lt;p&gt;I really like my current status bar setup but it took time and improvements to
get here.&lt;/p&gt;
&lt;p&gt;For instance I find the consistency is key for each block to be easily
identifiable: each block has its own color above and some empty space around it,
and its icon is always the first thing on the left followed by the text.&lt;/p&gt;
&lt;p&gt;I also take advantage of colors to reduce the amount of text needed on the
blocks. For example, the task block has three different numbers, but no label
for each, since each color already gives away their meaning.&lt;/p&gt;
&lt;p&gt;Reducing the amount of stuff in the status bar helped me focus in what is really
relevant at the moment. To achieve that I started questioning myself which
information really was important. I don't really care about which WiFi network
I'm connected to most of the time, and I used to have a block for that (and it's
a very common one), so I just ditched it. And there's information that is only
relevant sometimes, which is why I now have my blocks stay hidden unless their
current information is relevant.&lt;/p&gt;
&lt;p&gt;I also reduced the number of useless updates of the blocks by changing them to
update based on a signal instead of a timeout whenever possible. So for example
the keyboard block only updates when I press the binding to toggle the keyboard,
which sends a signal to this block.&lt;/p&gt;
&lt;p&gt;Hooking each block to a signal also makes the blocks update faster to
asynchronous events. For example, I update the packages in my computer using an
alias, which in addition to updating through pacman, sends a signal to the
update block at the end. So as soon as I have updated the packages, that block
disappears, and not after some timeout.&lt;/p&gt;
&lt;p&gt;Over time I've also written some code to make the block definition easier. As
with other parts of my system, I started my blocks in bash, but eventually
migrated them to python. As part of this I wrote a &lt;code class="docutils literal"&gt;print_i3blocks&lt;/code&gt; function
in python that receives the icon, text and color for the block, and outputs it
in the JSON format required by i3blocks.&lt;/p&gt;
&lt;p&gt;I also have a &lt;code class="docutils literal"&gt;update_i3blocks&lt;/code&gt; command that receives the name of the block
and sends the appropriate signal, so I can update specific blocks from other
commands in the system.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="the-one-annoying-bug"&gt;
&lt;h2&gt;The one annoying bug&lt;/h2&gt;
&lt;p&gt;When I migrated the block scripts to python I started having issues with the
blocks disappearing randomly. It took me almost a year to really dive into the
i3blocks code and find and &lt;a class="reference external" href="https://github.com/vivien/i3blocks/pull/454"&gt;fix this issue&lt;/a&gt;. Although it was hard to find, it
was also interesting to understand the issue. It didn't have anything to do with
python, it's just that since python code runs slower than bash, it became more
apparent.&lt;/p&gt;
&lt;p&gt;Unfortunately, since the i3blocks repository is no longer actively maintained,
if you're facing this same issue and want to fix it, you'll need to apply the
patch and compile it yourself (though that is is pretty simple). Nevertheless I
can't overstate how happy I am to finally get rid of this annoying bug. Now I
can just relax and enjoy my status bar showing all, and only, information that is
relevant to me 🙂.&lt;/p&gt;
&lt;/div&gt;
</content><category term="2021"/><category term="i3blocks"/><category term="desktop"/></entry><entry><title>Resurrecting a computer on the go</title><link href="https://nfraprado.net/post/resurrecting-a-computer-on-the-go.html" rel="alternate"/><published>2021-10-23T00:00:00-03:00</published><updated>2021-10-23T00:00:00-03:00</updated><author><name>Nícolas F. R. A. Prado</name></author><id>tag:nfraprado.net,2021-10-23:/post/resurrecting-a-computer-on-the-go.html</id><summary type="html">&lt;p&gt;Earlier this month I was spending a few weeks in another country. It was late at
night and I was once again looking over my personal files and thinking if there
was a better way to organize them in folders.&lt;/p&gt;
&lt;p&gt;After thinking for a bit I decided on a new …&lt;/p&gt;</summary><content type="html">&lt;p&gt;Earlier this month I was spending a few weeks in another country. It was late at
night and I was once again looking over my personal files and thinking if there
was a better way to organize them in folders.&lt;/p&gt;
&lt;p&gt;After thinking for a bit I decided on a new structure and moved some files
around. In the meantime, I realized I hadn't updated my packages for some time,
so I started an update.&lt;/p&gt;
&lt;p&gt;At the end of the update I saw an error. &lt;code class="docutils literal"&gt;/etc/mkinitcpio.conf&lt;/code&gt; was not found.
My &lt;code class="docutils literal"&gt;/etc/mkinitcpio.conf&lt;/code&gt; is a symlink to a file in my home as this makes it
easier to track the changes I do in that config as part of my backups. And
between the files I had just moved was precisely that config, so I updated the
symlink to point to the new location. I ran the update once again and &lt;code class="docutils literal"&gt;pacman&lt;/code&gt;
reported &amp;quot;nothing to be done&amp;quot;. I naively believed that everything was fine now.&lt;/p&gt;
&lt;p&gt;So I rebooted to make sure that everything was working with the files in the new
place. As I selected my system in GRUB, it reported that
&lt;code class="docutils literal"&gt;/initramfs-linux.img&lt;/code&gt; was missing. Same for the fallback initramfs. I was
officially locked out of my system.&lt;/p&gt;
&lt;p&gt;It was now clear to me what had happened. During the update the kernel was
updated, and as part of that the initramfs was re-generated, but since the
configuration file was invalid it was aborted. When I re-ran the update command,
&lt;code class="docutils literal"&gt;pacman&lt;/code&gt; only concluded that nothing had to be updated and said so. What I
should have done is to manually run the installation command again for the
kernel package to have the new initramfs generated. Since I didn't, there were
no initramfs files left on my boot partition, so I couldn't boot.&lt;/p&gt;
&lt;p&gt;I took a deep breath and started thinking what I could do. I only needed an USB
flash drive with an ArchLinux ISO to boot from and re-run the kernel package
installation to fix that mess. Since I was in a different country though, my
equipment was limited. Thankfully I did have an USB flash drive, but no other
computer to flash the ISO from.&lt;/p&gt;
&lt;p&gt;Unless... Unless I could use my beloved Nexus 5X phone to flash it. Its charging
port is an USB type C, and I suspected it worked as OTG as well. So I had the
USB flash drive and a USB A to type C dongle, I just had to discover if it was
possible to flash an ISO from the phone.&lt;/p&gt;
&lt;p&gt;I immediately opened F-Droid and searched for an ISO flasher. To my happiness I
discovered &lt;a class="reference external" href="https://f-droid.org/en/packages/eu.depau.etchdroid/"&gt;EtchDroid&lt;/a&gt;. Its description really resonated with me: &amp;quot;You can use it
to make a bootable GNU/Linux USB drive when your laptop is dead and you're in
the middle of nowhere.&amp;quot;. Seeing it might actually be possible, I went to the
Arch Linux page and downloaded the ISO.&lt;/p&gt;
&lt;p&gt;I then installed EtchDroid and opened it. I selected the option to flash an ISO,
selected the ISO file, plugged the USB flash drive to the phone and selected it
in the app. I really liked the simple UI:&lt;/p&gt;
&lt;img alt="{image}/etchdroid.png" src="/images/etchdroid/etchdroid.png" /&gt;
&lt;p&gt;I tapped the button to start flashing and a notification popped up showing the
progress:&lt;/p&gt;
&lt;img alt="{image}/flash_in_progress.png" src="/images/etchdroid/flash_in_progress.png" /&gt;
&lt;p&gt;And after a few seconds it was done:&lt;/p&gt;
&lt;img alt="{image}/flash_done.png" src="/images/etchdroid/flash_done.png" /&gt;
&lt;p&gt;I then unplugged the USB from the phone and plugged it to my laptop. I was
really happy to see that the USB image worked and booted from it.&lt;/p&gt;
&lt;p&gt;To fix the issue I set up my mountpoints, chrooted into my system, and did a
&lt;code class="docutils literal"&gt;pacman -S linux&lt;/code&gt; to reinstall the kernel package and have the initramfs
regenerated.&lt;/p&gt;
&lt;p&gt;With the initramfs back in place, I rebooted and everything was working fine
again 🙂.&lt;/p&gt;
&lt;p&gt;I'm really happy that despite my dumb mistake, I was able to solve it really
quickly even in a limited environment thanks to this nifty app called EtchDroid.
Definitely keeping this one around in case of future emergencies.&lt;/p&gt;
</content><category term="2021"/><category term="android"/><category term="linux"/></entry><entry><title>My journey to a good backup system</title><link href="https://nfraprado.net/post/my-journey-to-a-good-backup-system.html" rel="alternate"/><published>2021-09-27T00:00:00-03:00</published><updated>2021-09-27T00:00:00-03:00</updated><author><name>Nícolas F. R. A. Prado</name></author><id>tag:nfraprado.net,2021-09-27:/post/my-journey-to-a-good-backup-system.html</id><summary type="html">&lt;p&gt;I'm a bit of a data hoarder. I still have some of the first programs I've ever
written, photos I've taken on trips and drawings I did many years ago, to name a
few. And since I don't trust some company to store all of this data for me, it …&lt;/p&gt;</summary><content type="html">&lt;p&gt;I'm a bit of a data hoarder. I still have some of the first programs I've ever
written, photos I've taken on trips and drawings I did many years ago, to name a
few. And since I don't trust some company to store all of this data for me, it
was pretty clear that I needed to do my own backups.&lt;/p&gt;
&lt;p&gt;One lesson I learned early on reading online is that backups should be as easy
to do as possible, preferably completely autonomous, otherwise you end up not
doing them at all.&lt;/p&gt;
&lt;p&gt;In this post I want to recall my previous backup setup and then talk about the
current one. Hopefully it will be clear by the end that the current one is much
simpler and better.&lt;/p&gt;
&lt;div class="section" id="ye-olden-days"&gt;
&lt;h2&gt;Ye Olden Days&lt;/h2&gt;
&lt;p&gt;I think it was in 2018 that I started thinking seriously about making backups.
For that I needed both hardware and software. The hardware to house the backups,
and the software to make them happen.&lt;/p&gt;
&lt;div class="section" id="hardware"&gt;
&lt;h3&gt;Hardware&lt;/h3&gt;
&lt;p&gt;I had a desktop computer that no longer worked due to a problem in the
motherboard, but everything else worked fine so it was perfect to use for my
backup system. The hard drive could be used to store the backups, the power
supply to power everything, and the computer case would house all the
components.&lt;/p&gt;
&lt;p&gt;I started by removing the malfunctioning motherboard and noticed that the power
supply didn't give out any voltage by default. Searching online for its
datasheet I discovered which wires needed to be shorted for it turn on. Since
the original power button on the case was a push button that connected to the
motherboard, which in turn shorted these wires, I also removed this power button
and put a switch button in its place. I soldered the switch to the power supply
wires and fastened it to the case with some tape and thin metal plates I had
lying around to give it some rigidity. This is what it looks like:&lt;/p&gt;
&lt;img alt="{image}/button.jpg" src="/images/backup/button.jpg" /&gt;
&lt;p&gt;As for the main component, the computer that would run the backups, I had a
Raspberry Pi that wasn't being used so it was the obvious choice. I got a micro
USB connector from some old cable, opened the cable up, and soldered the wires
to the 5V and ground wires from the power supply, that way I could power
everything from the power supply, since the hard drive was already powered by
it.&lt;/p&gt;
&lt;p&gt;Finally, since the hard drive was an internal one, which uses a SATA connection
to transfer data, I bought a cheap SATA to USB converter cable and used it to
connect the hard drive to the Raspberry Pi. This is the whole setup:&lt;/p&gt;
&lt;img alt="{image}/internals.jpg" src="/images/backup/internals.jpg" /&gt;
&lt;p&gt;(This is a current picture, it didn't have the Ethernet cable at the time)&lt;/p&gt;
&lt;p&gt;That was it for the hardware setup, next was the software solution to make the
backups happen.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="software"&gt;
&lt;h3&gt;Software&lt;/h3&gt;
&lt;p&gt;As I was thinking about which software to use for my backups, I stumbled upon
&lt;a class="reference external" href="https://opensource.com/article/18/8/automate-backups-raspberry-pi"&gt;this article&lt;/a&gt;. It presented to me the idea of incremental backups (only
storing the differences in files from the previous backup), and backup rotation
(only storing the N latest backups and deleting the older ones) which I found
genius. It also relied mostly on rsync for the copies, which I was already
familiar with. In the post they write a shell script to do the backup and
retention, but I saw a comment mentioning that there was a tool that did exactly
that, called &lt;a class="reference external" href="https://rsnapshot.org/"&gt;rsnapshot&lt;/a&gt;. So I went for rsync + rsnapshot as my backup solution.&lt;/p&gt;
&lt;p&gt;Very soon I faced some issues. The biggest one was that rsnapshot could only
backup to a local storage. This meant that it needed to run on the Raspberry Pi,
which was connected to the hard drive where the backups would be stored, and
backup the files from my laptop.&lt;/p&gt;
&lt;p&gt;Since the raspberry needed to connect to my computer, the normal thing to do
would be to set a fixed IP for my laptop, but since I carried it with me to
other networks, I thought it would be best to keep it with a dynamic IP. So I
ended up with an ugly solution where my computer had the cronjob for the backup,
in which it connected to the raspberry, updated its IP there and ran rsnapshot
there. rsnapshot then used that updated IP to connect back to my computer and
make the backup.&lt;/p&gt;
&lt;p&gt;To make the data safe, the disk was encrypted using dm-crypt. This means that
before rsnapshot began the backup, the disk was unlocked using the passphrase,
which was saved as a file in the raspberry's system (very unsafe, I know!).
After the backup it was re-locked and powered off.&lt;/p&gt;
&lt;p&gt;One other issue is that rsync doesn't deal well with file/directory renaming or
moving. It thinks that the previous one was deleted and a new one created. This
was a big problem since I was always trying to find the best hierarchy for my
files, which meant renaming or moving around the base directories and then
suddenly all files had to be copied again...&lt;/p&gt;
&lt;p&gt;That issue was further aggravated by the fact that the transfer between my
computer and the raspberry was done wirelessly, so rather slow. To workaround
this I added some logic to the backup scripts so that if the total size of the
transfer was too big, it would just fail and notify me. I would then look at the
diff, remember that I had done some renaming on the directories and copy them
over manually to the backup through a temporary Ethernet cable.&lt;/p&gt;
&lt;p&gt;Lastly, since the power supply was very noisy and I also didn't want to keep it
constantly on, I'd have a daily alarm on my phone to remind myself to flip the
little switch to turn it on just before the daily backup cronjob. And I'd turn
it off later before going to sleep.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="nowadays"&gt;
&lt;h2&gt;Nowadays&lt;/h2&gt;
&lt;p&gt;Some time has passed since then, and given that that setup worked but it was far
from good, I've done some more research on the backup tools out there to see if
there was anything better. Everything changed when I found &lt;a class="reference external" href="https://www.borgbackup.org/"&gt;borg&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;a class="reference external" href="https://www.borgbackup.org/"&gt;Borg&lt;/a&gt;, or BorgBackup, is a program to make backups. It can backup locally or
remotely. The backups can (and should!) be encrypted. And deduplicated! I'm not
sure if renaming/moving files makes the backup take longer, but at least it
won't take more space since all files are chunked and deduplicated independently
of their path. Oh, and there's great documentation and an active community!&lt;/p&gt;
&lt;p&gt;On top of that, there's another project called &lt;a class="reference external" href="https://torsion.org/borgmatic/"&gt;borgmatic&lt;/a&gt;. Borgmatic provides a
single configuration file to configure borg's usage, like the repositories to
use, retention policy and files to ignore for backups. So basically while borg
provides the commands to do backups, borgmatic allows you to configure it all in
a single file and it takes care of everything else.&lt;/p&gt;
&lt;p&gt;In the end, borg + borgmatic make it incredibly easy to have secure and reliable
backups. I now have a single cron entry to do my daily backups: &lt;code class="docutils literal"&gt;0 21 * * * cd
~/ &amp;amp;&amp;amp; borgmatic --no-color --verbosity 1 --files &amp;gt;&amp;gt; ~/ark/etc/bkplogs&lt;/code&gt;. It's
important to note that borg always uses relative paths in backups, so that's why
I &lt;code class="docutils literal"&gt;cd&lt;/code&gt; to where the folders I want to backup are first (my home directory).&lt;/p&gt;
&lt;p&gt;Also since all the backup configuration is done in a single configuration file
with borgmatic, it's easy to backup that file as well. So if I ever lose my
files and need to recover from a backup, I'll immediately also have the backup
setup ready to go again.&lt;/p&gt;
&lt;p&gt;So yeah, borg is pretty much great and a whole lot better than any other
solutions I tried before. There's a single catch though, if you're doing a
backup to a remote location, borg also needs to be running there. So in these
cases you have borg running on the local machine, doing the chunking and
encrypting (so the remote already receives it encrypted and it's safe!), and on
the remote machine another borg instance will be listening and storing the
chunks in the archive.&lt;/p&gt;
&lt;p&gt;Given that restriction, you can't backup to a remote that is a simple storage on
the cloud. There are, however, very good alternatives focused on hosting borg
backups, like &lt;a class="reference external" href="https://www.borgbase.com/"&gt;BorgBase&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Now, I still use my raspberry setup, although I've now installed borg there
instead of rsnapshot, and removed all my weird scripts. It's now just an
additional entry in my borgmatic config. Also instead of having the disk
password lying in the raspberry system I had the idea to send it as part of the
ssh command, that way I can store it securely in my own encrypted machine. Oh,
and I now rely on a remote server for daily backups and only do local backups
once a week, that way I don't need to have the trouble of flipping that little
switch every day 😉.&lt;/p&gt;
&lt;p&gt;It's worth mentioning that recovery of backups is also very easy with borg.
There are two commands (offered by both borg and borgmatic): &lt;code class="docutils literal"&gt;extract&lt;/code&gt; and
&lt;code class="docutils literal"&gt;mount&lt;/code&gt;. &lt;code class="docutils literal"&gt;extract&lt;/code&gt; recovers the file(s) from a given archive. &lt;strong&gt;Note that it
will be saved in the same path as used in the archive, potentially overwriting
your local files&lt;/strong&gt;, so better run it inside a temporary folder first. &lt;code class="docutils literal"&gt;mount&lt;/code&gt;
mounts the archive on a local folder as if it were a filesystem, allowing you to
browse the files.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="conclusion"&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;As you can see, I've significantly improved my backup setup. And that's because
it is less interesting, not the opposite. As much as I have fond memories of
having my own system, which relied on multiple scripts to solve the issues with
what I had, it was a hassle to maintain. It's great to have everything just work
out-of-the-box with borg and borgmatic: I can even forget I'm actually doing
backups now, which is refreshing.&lt;/p&gt;
&lt;/div&gt;
</content><category term="2021"/><category term="backup"/><category term="borg"/></entry><entry><title>Owning my Kindle</title><link href="https://nfraprado.net/post/owning-my-kindle.html" rel="alternate"/><published>2021-08-29T00:00:00-03:00</published><updated>2021-08-29T00:00:00-03:00</updated><author><name>Nícolas F. R. A. Prado</name></author><id>tag:nfraprado.net,2021-08-29:/post/owning-my-kindle.html</id><summary type="html">&lt;p&gt;Several years ago my mother gave me a Kindle Paperwhite 2. I have read a few
books on it since, but it never felt like it was really mine. Locking the screen
showed some annoying ad, all books bought from Amazon were protected by DRM,
books transferred through USB only …&lt;/p&gt;</summary><content type="html">&lt;p&gt;Several years ago my mother gave me a Kindle Paperwhite 2. I have read a few
books on it since, but it never felt like it was really mine. Locking the screen
showed some annoying ad, all books bought from Amazon were protected by DRM,
books transferred through USB only worked if they were in Amazon's .mobi format,
and there was always the unease of knowing that every page I flipped could be
being reported back to their headquarters.&lt;/p&gt;
&lt;p&gt;Recently, I decided to put a stop to it and find out how to jailbreak my Kindle.
I was willing to either make it mine or lose it in the process.&lt;/p&gt;
&lt;div class="section" id="jailbreaking"&gt;
&lt;h2&gt;Jailbreaking&lt;/h2&gt;
&lt;p&gt;A little bit of search gave me &lt;a class="reference external" href="https://www.mobileread.com/forums/showthread.php?t=320564"&gt;a very well maintained forum post on how to
jailbreak the kindle&lt;/a&gt;. In fact that mobileread.com forum seems to be the focal
point on e-book reader devices jailbreaking and customizations. It's wonderful
how there are these bubbles on the internet filled with specific information
which you never find out until you search for it.&lt;/p&gt;
&lt;p&gt;I started following along the guide, which is actually very simple: the
jailbreak itself consists of plugging the Kindle through the USB to the
computer, dropping a file there and getting it to run. At this point however, I
stumbled upon a big problem which I had already suspected: the USB in the Kindle
no longer worked.&lt;/p&gt;
&lt;p&gt;The micro USB port on the Kindle should serve both for power delivery to
recharge the device as well as data transmission, to be able to mount it on the
computer as a data storage and send files. But somehow the data transmission no
longer worked, so I couldn't send files over to the Kindle. At least power still
worked, so I could still recharge it.&lt;/p&gt;
&lt;p&gt;With the main data channel to the Kindle borked I realized my alternatives were
either to find out the issue with USB, which I thought would be a USB controller
hardware issue, and require swapping the IC with a new one, or checking if the
Kindle had a working UART interface I could use instead of the USB. The latter
seemed way easier if it was possible.&lt;/p&gt;
&lt;div class="section" id="jailbreaking-through-the-uart"&gt;
&lt;h3&gt;Jailbreaking through the UART&lt;/h3&gt;
&lt;p&gt;Indeed it was possible to access the UART in the Kindle PW2 as &lt;a class="reference external" href="https://www.mobileread.com/forums/showthread.php?t=267541"&gt;this post&lt;/a&gt; shows. It also shows how to open the Kindle
and gives some ideas on making the setup permanent.&lt;/p&gt;
&lt;p&gt;The only hard part in opening was the first step: getting the front cover to
unglue from the rest. After stopping to play the guitar a few years ago, I never
thought this was the way a guitar pick would come to use again 😝:&lt;/p&gt;
&lt;img alt="{image}/open-pick.jpg" src="/images/kindle-jailbreak/open-pick.jpg" /&gt;
&lt;p&gt;With the help of the pick I got the cover unglued and after unscrewing a few
screws I got to the insides (the second photo shows the PCB screws also
unscrewed and flat cables disconnected since I was looking around, but there's
nothing of interest below the PCB):&lt;/p&gt;
&lt;img alt="{image}/open-nocover.jpg" src="/images/kindle-jailbreak/open-nocover.jpg" /&gt;
&lt;img alt="{image}/open-board.jpg" src="/images/kindle-jailbreak/open-board.jpg" /&gt;
&lt;p&gt;At this point I was ready to test if the UART really worked. One issue was that
the Kindle's UART works at 1.8V while my UART cable is 3.3V. To workaround this
I used a simple voltage divider to lower the voltage for the Kindle's RX, and
two bipolar transistors and a voltage regulator to increase the voltage of the
Kindle's TX (I have to admit that I searched the web for this circuit, but at
least after seeing it I was able to verify how it worked and will remember it
for future projects 🙂). This was the temporary setup:&lt;/p&gt;
&lt;img alt="{image}/temp-mod-uart.jpg" src="/images/kindle-jailbreak/temp-mod-uart.jpg" /&gt;
&lt;p&gt;With this I ran &lt;code class="docutils literal"&gt;picocom&lt;/code&gt; on the serial port and rebooted the Kindle. This is
what I got (trimmed down to not be too long):&lt;/p&gt;
&lt;pre class="code literal-block"&gt;
Terminal ready
Battery voltage: 4072 mV

Hit any key to stop autoboot:  0 
## Booting kernel from Legacy Image at 80800000 ...
   Image Name:   Linux-3.0.35-lab126
   Image Type:   ARM Linux Kernel Image (uncompressed)
   Data Size:    3054728 Bytes =  2.9 MB
   Load Address: 80008000
   Entry Point:  80008000
   Verifying Checksum ... OK
   Loading Kernel Image ... OK
OK

Starting kernel ...

[    0.097278] boot: C def:bcut:batterycut=1
[    0.274436] LPDDR2 MfgId: 0x1 [Samsung]

[...]

3.0.35-lab126 #2 PREEMPT Wed Sep 25 00:44:40 UTC 2019 armv7l
Press [ENTER] for recovery menu...       0 /BOOTING DEFAULT.
IP-Config: no devices to configure
kinit: Mounted root (ext3 filesystem) readonly.

[...]

info milestone:7.91:sy99:sy99
info system:done:time=7940:time=7940
crond[849]: crond (busybox 1.28.3) started, log level 8

init.exe: sshd main process (848) terminated with status 127
init.exe: sshd ain process endeinit.exe: recevnt pre-start proc


Welcome to Kindle!

kindle login: info milestone:8.24:sc01:sc01
info system_fs_loopbacks:mountingfs:Mounting compressed directories filesystem images:Mounting compressed directories filesystem images
info milestone:8.27:sc02:sc02
info milestone:8.31:/usr/share/X11/xkb:/usr/share/X11/xkb

[...]

info clickstreamHeartbeatMetricsFramework:Skipping metrics recording, as it is the same day::
info monitor:writing_file:file=/var/run/upstart/stored.restarts:file=/var/run/upstart/stored.restarts
Retrieved 383 keys for system/daemon/pmond/
&lt;/pre&gt;
&lt;p&gt;Well, hello Linux 3.0, how long have you been there? 🙂&lt;/p&gt;
&lt;p&gt;And my input worked too so I was ready to keep going with the jailbreak. I
kept following the &lt;a class="reference external" href="https://www.mobileread.com/forums/showthread.php?t=267541"&gt;jailbreak through serial post&lt;/a&gt; until I got root, but to my
surprise after that it shows how to transfer the jailbreak file by using the USB
storage, which I don't have working. But since I just got the serial working, I
didn't see why I couldn't use it to transfer a file, even though I had never
done that.&lt;/p&gt;
&lt;p&gt;Sure enough I found a way, and it's by using the xmodem, ymodem or zmodem
file transfer protocols. All of them are provided under the &lt;a class="reference external" href="https://archlinux.org/packages/community/x86_64/lrzsz/"&gt;lrzsz&lt;/a&gt; package on
Arch Linux. Since the Kindle only had the &lt;code class="docutils literal"&gt;rx&lt;/code&gt; binary, which is the receiver
for xmodem, I needed to use &lt;code class="docutils literal"&gt;sx&lt;/code&gt; on my computer, to send using the xmodem
protocol as well.&lt;/p&gt;
&lt;p&gt;The procedure to send a file through the serial using xmodem is to:&lt;/p&gt;
&lt;ul class="simple" id="xmodem-procedure"&gt;
&lt;li&gt;run &lt;code class="docutils literal"&gt;picocom&lt;/code&gt; passing &lt;code class="docutils literal"&gt;--send-cmd 'sx'&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;in the serial (the Kindle's shell in my case), type &lt;code class="docutils literal"&gt;rx &amp;lt;filename&amp;gt;&lt;/code&gt; where
&lt;code class="docutils literal"&gt;filename&lt;/code&gt; is the name that will be given to the received file in the
destination filesystem.&lt;/li&gt;
&lt;li&gt;press &lt;code class="docutils literal"&gt;Ctrl+a&lt;/code&gt; followed by &lt;code class="docutils literal"&gt;Ctrl+s&lt;/code&gt;. This will instruct picocom to send a
file, which will use &lt;code class="docutils literal"&gt;sx&lt;/code&gt; for the transfer since we passed that as the
&lt;code class="docutils literal"&gt;--send-cmd&lt;/code&gt; when running picocom.&lt;/li&gt;
&lt;li&gt;in the prompt that appears, write the name of the file that will be
transferred from the host machine, relative to the directory where picocom was
run.&lt;/li&gt;
&lt;li&gt;wait for the transfer to complete (may take a while).&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Now, there's an important detail about transferring files like this that took me
a while to find out, and may only be applicable to my specific setup, I'm not
sure, but it is that &lt;strong&gt;the transfer will only work if the Kindle is charging&lt;/strong&gt;.
For some reason, it seems that when the Kindle isn't charging, the TX/RX signals
aren't stable enough to the point that the checksums sent in-between the xmodem
transfer aren't valid and the transfer fails. Using the shell without power
connected works well enough though.&lt;/p&gt;
&lt;p&gt;But with the charger connected and following the above steps, I was always able
to transfer the files successfully.&lt;/p&gt;
&lt;p&gt;It does take a while to transfer this way though. The transfer speed is about 10
KB/s, so the jailbreak file (~160 KB) took 15 seconds to transfer, while larger
files like the KOReader package (~37 MB), which I mention below, took about one
hour. Through USB the former would have been instantaneous, while the latter
might have taken a couple seconds. That made me realize how far we've come in
transfer speeds, so it has its charm 😝.&lt;/p&gt;
&lt;p&gt;With this setup I was able to transfer the file and follow the remaining of the
guide to successfully jailbreak my Kindle! I also installed KUAL and MRInstaller
as directed in the guides so that packages are easy to install and can be
accessed through a GUI in the Kindle.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="making-it-mine"&gt;
&lt;h2&gt;Making it mine&lt;/h2&gt;
&lt;p&gt;Now that I had a jailbroken Kindle it was time to finally make it mine. The need
for the .mobi format is annoying, I'd prefer to use a more standard format like
epub. Fortunately that's possible on a jailbroken Kindle by using the &lt;a class="reference external" href="https://github.com/koreader/koreader"&gt;KOReader&lt;/a&gt;
application.&lt;/p&gt;
&lt;div class="section" id="koreader"&gt;
&lt;h3&gt;KOReader&lt;/h3&gt;
&lt;p&gt;&lt;a class="reference external" href="https://github.com/koreader/koreader"&gt;KOReader&lt;/a&gt; is an e-book reader application that can be run on jailbroken Kindles
(as well as other devices) and supports multiple e-book formats like epub and
pdf, which is exactly what I wanted.&lt;/p&gt;
&lt;p&gt;It's honestly great software: It is very actively maintained and improved, and
has all the features I was used to in the default Kindle reader like
bookmarking, dictionary search, text search and navigation through the book
chapters, plus a lot more and configurations inside its various menus.&lt;/p&gt;
&lt;p&gt;My favorite features, which are improvements over the default Kindle reader, are
the progress bar at the bottom of the screen showing where you are through the
book, and the fact that when the device is locked, the cover of the book is
shown in the screen! 😃 This made me super happy to see. No more ads or random
pictures from Amazon, just the cover of the book I'm reading. Actually, I
originally installed the ScreenSavers Hack to be able to set a custom
screensaver right after I jailbreaked the Kindle, but after I noticed that
KOReader had this feature built-in I removed that package.&lt;/p&gt;
&lt;p&gt;My only issue with KOReader is that sometimes it gets really laggy, so I need to
restart it (which takes just a couple seconds) and it gets back to normal speed.
But I should probably update it since they release a new update every month
and it's already been four months since I installed it, so the issue may already
have been fixed. (Yes, everything described in this post happened around April)&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="uart-header"&gt;
&lt;h3&gt;UART header&lt;/h3&gt;
&lt;p&gt;In order to make the Kindle surveillance-proof I had to keep the WiFi always off
(also to keep the jailbreak working). And since transferring through the USB
doesn't work anymore, the UART would need to be my only interface with the
Kindle. This means that I need to be able to transfer books to the Kindle
through the UART with relative ease. I don't consider having to open it relative
ease, so I needed to make a more definitive setup.&lt;/p&gt;
&lt;p&gt;The idea was basically to make the wires connected to the PCB pads for the UART
better fixed, open a hole in the Kindle's plastic cover on the side and expose
some pins there so I could easily connect a UART cable to the Kindle without
having to open it.&lt;/p&gt;
&lt;p&gt;One issue I already mentioned is that my UART cable's operating voltage is
different from the Kindle's, which is why I made the converter circuit on the
breadboard. I initially thought of embedding the converter circuit inside the
Kindle, but realized I didn't have parts small enough for it to fit there. So I
ended up just accepting that I'd need to use a converter circuit externally.&lt;/p&gt;
&lt;p&gt;The opening in the case to expose the serial pins was done by my father! 😃&lt;/p&gt;
&lt;img alt="{image}/mod-case.jpg" src="/images/kindle-jailbreak/mod-case.jpg" /&gt;
&lt;img alt="{image}/mod-case2.jpg" src="/images/kindle-jailbreak/mod-case2.jpg" /&gt;
&lt;img alt="{image}/mod-case3.jpg" src="/images/kindle-jailbreak/mod-case3.jpg" /&gt;
&lt;p&gt;The soldering of the wire in the pads on the PCB was a bit tricky due to the
very small size of the pads. With a little help from tape to fix the wire while
I soldered and by choosing the right wire (those used inside Ethernet cables,
they're the right amount of flexible) I managed to do it.&lt;/p&gt;
&lt;p&gt;I was still worried about accidentally hitting the wire and disconnecting it, or
even worse, irreversibly damaging the pad, so I ended up taping the wires every
so often to the board, like checkpoints, and it worked wonderfully. I'll
certainly keep using this technique in the future.&lt;/p&gt;
&lt;img alt="{image}/mod-uart-connection.jpg" src="/images/kindle-jailbreak/mod-uart-connection.jpg" /&gt;
&lt;p&gt;I left some extra length in the wire just in case it is needed in the future.&lt;/p&gt;
&lt;img alt="{image}/mod-uart-wires.jpg" src="/images/kindle-jailbreak/mod-uart-wires.jpg" /&gt;
&lt;p&gt;Getting the wire well connected to the headers was tricky as well. I twisted
each wire around each pin, but not very tightly so the wire didn't break (this
happened a few times), and added some solder. After that I added electrical tape
around each pin so the wires around each pin didn't touch each other. Finally I
added some more tape around all of the pins to isolate them from the gray body
of the Kindle, since that's a conductive material.&lt;/p&gt;
&lt;img alt="{image}/mod-uart-header.jpg" src="/images/kindle-jailbreak/mod-uart-header.jpg" /&gt;
&lt;p&gt;After getting the pins reasonably centered in the slot, I added some &lt;a class="reference external" href="https://www.loctite-consumo.com.br/pt/produtos/durepoxi.html"&gt;durepoxi&lt;/a&gt;
(a solid epoxy common in Brazil) and molded it all around the pins, above and
below, to get them fixed to the Kindle's body.&lt;/p&gt;
&lt;img alt="{image}/mod-uart-epoxy.jpg" src="/images/kindle-jailbreak/mod-uart-epoxy.jpg" /&gt;
&lt;p&gt;After letting it dry overnight, the surgery was done:&lt;/p&gt;
&lt;img alt="{image}/mod-final.jpg" src="/images/kindle-jailbreak/mod-final.jpg" /&gt;
&lt;p&gt;Oh, and all of this took me two tries. The first time I got to the end but some
pins were shorted through the gray body of the Kindle. Thankfully the epoxy
wasn't completely rigid so I could remove it with some perseverance and try
again.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="result"&gt;
&lt;h2&gt;Result&lt;/h2&gt;
&lt;p&gt;So now I have a Kindle with the UART pins exposed, this is what it looks like:&lt;/p&gt;
&lt;img alt="{image}/result-pin.jpg" src="/images/kindle-jailbreak/result-pin.jpg" /&gt;
&lt;img alt="{image}/result-front.jpg" src="/images/kindle-jailbreak/result-front.jpg" /&gt;
&lt;p&gt;Now if I want to read a new book, I connect the charger and the converter board
to it (I'm still using a breadboard for the converter circuit because I've been
too lazy to make a proper board, but it wouldn't be too hard and would be a lot
more compact):&lt;/p&gt;
&lt;img alt="{image}/result-transfer-setup.jpg" src="/images/kindle-jailbreak/result-transfer-setup.jpg" /&gt;
&lt;p&gt;I then follow the &lt;a class="reference internal" href="#xmodem-procedure"&gt;xmodem procedure&lt;/a&gt; I mentioned above to send the book file
through UART and wait a bit for the transfer to complete...&lt;/p&gt;
&lt;pre class="code literal-block"&gt;
[root&amp;#64;kindle documents]# rx sherlock_holmes.epub
C
*** file: ext/books/sherlock_holmes.epub
$ sx ext/books/sherlock_holmes.epub
Sending ext/books/sherlock_holmes.epub, 16078 blocks: Give your local XMODEM receive command now.
Bytes Sent:2058112   BPS:10396                           

Transfer complete

*** exit status: 0 ***
[root&amp;#64;kindle documents]#
&lt;/pre&gt;
&lt;p&gt;This book took about 3 minutes to transfer. Usual epub book sizes are between
100 KB to 10 MB, so transferring a new book takes from 10 seconds to 20 minutes
depending on the book. It's significantly more time than it would've been
through USB (a couple seconds) but since I take between one and two months to
finish each book it's not a problem.&lt;/p&gt;
&lt;p&gt;When the transfer is done, all that is left to do is unhook the cables and enjoy
the reading! 🙂&lt;/p&gt;
&lt;img alt="{image}/result-book.jpg" src="/images/kindle-jailbreak/result-book.jpg" /&gt;
&lt;p&gt;(I use that blue cover over the Kindle to protect it and the pins are short
enough that they fit under it as well)&lt;/p&gt;
&lt;p&gt;Note: That book is on public domain and can be &lt;a class="reference external" href="https://www.gutenberg.org/ebooks/48320"&gt;downloaded for free on Project
Gutenberg&lt;/a&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="conclusion"&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;It took a bit more doing than I originally expected, but after all this I'm
happy that I finally really own my Kindle and can have a better reading
experience on it 🙂.&lt;/p&gt;
&lt;/div&gt;
</content><category term="2021"/><category term="kindle"/></entry><entry><title>Blog customizations</title><link href="https://nfraprado.net/post/blog-customizations.html" rel="alternate"/><published>2021-07-24T00:00:00-03:00</published><updated>2021-07-24T00:00:00-03:00</updated><author><name>Nícolas F. R. A. Prado</name></author><id>tag:nfraprado.net,2021-07-24:/post/blog-customizations.html</id><summary type="html">&lt;p&gt;I've been using &lt;a class="reference external" href="https://github.com/getpelican/pelican/"&gt;pelican&lt;/a&gt; as my static blog generator since I started this blog
about one year ago. It only took a bit of configuration to get the blog up and
running, and a bit of searching through &lt;a class="reference external" href="http://pelicanthemes.com/"&gt;pelicanthemes&lt;/a&gt; to find &lt;a class="reference external" href="https://github.com/gunchu/nikhil-theme"&gt;nikhil-theme&lt;/a&gt;
which is the theme I'm still using.&lt;/p&gt;
&lt;p&gt;While …&lt;/p&gt;</summary><content type="html">&lt;p&gt;I've been using &lt;a class="reference external" href="https://github.com/getpelican/pelican/"&gt;pelican&lt;/a&gt; as my static blog generator since I started this blog
about one year ago. It only took a bit of configuration to get the blog up and
running, and a bit of searching through &lt;a class="reference external" href="http://pelicanthemes.com/"&gt;pelicanthemes&lt;/a&gt; to find &lt;a class="reference external" href="https://github.com/gunchu/nikhil-theme"&gt;nikhil-theme&lt;/a&gt;
which is the theme I'm still using.&lt;/p&gt;
&lt;p&gt;While the base blog setup was easily done, it wasn't perfectly suited for my
needs. This is when I started customizing a few things to make it fit my use.
Thankfully pelican is &lt;a class="reference external" href="https://docs.getpelican.com/en/stable/index.html"&gt;really customizable&lt;/a&gt;, even offering a plugin interface
in addition to the normal configurations. It is also written in python, so if
all fails, I can always hack a patch together to get the job done 😝.&lt;/p&gt;
&lt;p&gt;So as promised in the previous post, in this one I will talk about the main
customizations I did to my blog so far. Throughout the text I'll link to the
commits where I did the changes mentioned here.&lt;/p&gt;
&lt;div class="section" id="customizations"&gt;
&lt;h2&gt;Customizations&lt;/h2&gt;
&lt;div class="section" id="pelican-plugins"&gt;
&lt;h3&gt;Pelican plugins&lt;/h3&gt;
&lt;p&gt;The most impactful customizations I did were probably the pelican plugins I
added. These aren't my own modifications, but given their importance I want to
describe them.&lt;/p&gt;
&lt;p&gt;The two plugins I'm currently using are &lt;a class="reference external" href="https://github.com/getpelican/pelican-plugins/tree/master/i18n_subsites"&gt;i18n_subsites&lt;/a&gt; and &lt;a class="reference external" href="https://github.com/pelican-plugins/series"&gt;series&lt;/a&gt;, and they
were really easy to find from the &lt;a class="reference external" href="https://github.com/getpelican/pelican-plugins/"&gt;plugin repository&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;i18n_subsites is great for my dual-language blog. By default, pelican supports
only translating blog posts as standalone texts so that each translated post has
links the translations. But what I really wanted was to have two versions of
my blog, one in english and another in portuguese. Each with all the interface
in its own language as well as only showing the posts of that language. This
plugin enables exactly that.&lt;/p&gt;
&lt;p&gt;It is a bit confusing to understand how to correctly configure it at first,
mainly the &lt;a class="reference external" href="https://github.com/getpelican/pelican-plugins/blob/master/i18n_subsites/localizing_using_jinja2.rst"&gt;template localization part&lt;/a&gt;, but hey, I learned about Jinja2 and
gettext! Relevant commits: &lt;a class="reference external" href="https://codeberg.org/nfraprado/blog/commit/f538d7ebf78f9b93dd046d6aa44eaae08b287b66"&gt;1&lt;/a&gt; (it was part of the initial commit, sorry), &lt;a class="reference external" href="https://codeberg.org/nfraprado/blog/commit/9a02ff6bd640ea3c79e9e66f72b68116bed17b38"&gt;2&lt;/a&gt;,
&lt;a class="reference external" href="https://codeberg.org/nfraprado/blog/commit/604ca272908e77d50bc459ba51f45e47fbfb30bd"&gt;3&lt;/a&gt;, &lt;a class="reference external" href="https://codeberg.org/nfraprado/blog/commit/c2effc722cf01b834c7f2373bd6ef8791e4adc7d"&gt;4&lt;/a&gt; and &lt;a class="reference external" href="https://codeberg.org/nfraprado/blog/commit/d23b1151f12db58a9f9b4bbdbd9389cf6fdf53e4"&gt;5&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;series I added a bit later. It's very simple but useful. In fact you can see it
in action in this same post at the top. It's what enables me to make this post
part of a series of posts and link to the other ones. Relevant commits: &lt;a class="reference external" href="https://codeberg.org/nfraprado/blog/commit/b6f481df611fa1b2e0815e2eeaf2b0a7a8016f26"&gt;1&lt;/a&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="separate-rss-feed-for-each-i18n-sub-site"&gt;
&lt;h3&gt;Separate RSS feed for each i18n sub-site&lt;/h3&gt;
&lt;p&gt;Also on the subject of translation, since I wanted separate sub-sites for each
language, it also made sense to have a separate RSS feed for each of them. In
the end this turned out to be a single line change, even though it took me a
while to figure out how to do it. Relevant commits: &lt;a class="reference external" href="https://codeberg.org/nfraprado/blog/commit/dee307122f54e04f9aaa5a203b29a9c029a5f88f"&gt;1&lt;/a&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="automatic-language-detection-from-filename"&gt;
&lt;h3&gt;Automatic language detection from filename&lt;/h3&gt;
&lt;p&gt;Another customization related to translations (it was a big thing for my blog!
😝) was setting the language of the post automatically from the file name. Since
I already want the file for each translation of a post have the language in its
name, this saves me from needing to input the language header as well.&lt;/p&gt;
&lt;p&gt;For example, the files for this post are called &lt;code class="docutils literal"&gt;07-blog-custom-en.rst&lt;/code&gt;, for
the english version, and &lt;code class="docutils literal"&gt;07-blog-custom-br.rst&lt;/code&gt; for the portuguese version.
Normally I'd also need to set a &lt;code class="docutils literal"&gt;lang&lt;/code&gt; metadata header for each one with the
corresponding language, but with this change it is set automatically 🙂.
Relevant commits: &lt;a class="reference external" href="https://codeberg.org/nfraprado/blog/commit/f538d7ebf78f9b93dd046d6aa44eaae08b287b66#4cd8c938f4dfb9b2579dcfaf97c623df6ca28eb8_0_23"&gt;1&lt;/a&gt; (again, part of the initial commit 😅) and &lt;a class="reference external" href="https://codeberg.org/nfraprado/blog/commit/1712f5b44b2d51de410348462c9d8a66d8b210fa"&gt;2&lt;/a&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="icon-setting-for-pages"&gt;
&lt;h3&gt;Icon setting for pages&lt;/h3&gt;
&lt;p&gt;When I was setting up the navbar on the top, I wanted to add an icon to each
entry, to make it very clear what each one does. But the About page, unlike the
others, is a page generated from rst and automatically added to the bar, so I
couldn't set its icon in the HTML directly.&lt;/p&gt;
&lt;p&gt;To solve this I created a custom &lt;code class="docutils literal"&gt;icon&lt;/code&gt; metadata header that holds the
FontAwesome icon name for the icon that will be displayed for the page on the
bar. Then I just added the name of that &amp;quot;info circle&amp;quot; icon for the About page
and now it looks much better 🙂. Relevant commits: &lt;a class="reference external" href="https://codeberg.org/nfraprado/blog/commit/76534d397a83117327aa1381241b412c77a47b7b"&gt;1&lt;/a&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="fix-for-literal-rendering-in-rst"&gt;
&lt;h3&gt;Fix for literal rendering in rst&lt;/h3&gt;
&lt;p&gt;Let's wrap the post with the biggest change 😝.&lt;/p&gt;
&lt;p&gt;First of all, some context: When I started the blog, I wrote the posts in
Markdown. But a few months later I learned about &lt;a class="reference external" href="https://docutils.sourceforge.io/rst.html"&gt;reStructuredText&lt;/a&gt; (from here on,
referred to as rst) because it was used in the Linux Kernel documentation, and
started using it even more when I discovered &lt;a class="reference external" href="https://rst2pdf.org/"&gt;rst2pdf&lt;/a&gt;, which I won't go into
detail since I still plan on writing about it eventually. The point is that I
started writing my blog posts in rst instead since it was supported by pelican.&lt;/p&gt;
&lt;p&gt;But one really annoying issue I faced is that the default HTML generator for rst
translates literal blocks into &lt;code class="docutils literal"&gt;&amp;lt;tt&amp;gt;&lt;/code&gt; tags. This tag is deprecated in HTML5,
and the whole world uses &lt;code class="docutils literal"&gt;&amp;lt;code&amp;gt;&lt;/code&gt; for inline code, including my theme, which
meant literals didn't render right. (&lt;a class="reference external" href="https://sourceforge.net/p/docutils/mailman/docutils-develop/thread//p/docutils/bugs/393/de24b5cc7539766427b108223bbcedb8ea8aef5f.bugs%40docutils.p.sourceforge.net/#msg37057965"&gt;There are reasons&lt;/a&gt; rst doesn't use
&lt;code class="docutils literal"&gt;&amp;lt;code&amp;gt;&lt;/code&gt; for literals, but for my blog they aren't relevant)&lt;/p&gt;
&lt;p&gt;I could have just adapted my theme to workaround it, but that would be hiding
the problem: The deprecated &lt;code class="docutils literal"&gt;&amp;lt;tt&amp;gt;&lt;/code&gt; tag would still be there for the world to
see.&lt;/p&gt;
&lt;p&gt;At first, I worked around this issue by using the code role explicitly:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
The following is a rst literal, so it will be inside a &amp;lt;tt&amp;gt; tag: ``blah``
The following uses the code role, so it will be inside a &amp;lt;code&amp;gt; tag: :code:`blah`
&lt;/pre&gt;
&lt;p&gt;That is obviously a too cumbersome syntax for a blog that frequently uses inline
code, so I got tired of it quickly. I then started overriding the default role
to be &lt;code class="docutils literal"&gt;code&lt;/code&gt;, and using single ticks instead of the double ticks syntax for
literals:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
.. default-role:: code

Now the following will use the code role: `blah`
&lt;/pre&gt;
&lt;p&gt;Since the text is inside single ticks but doesn't have a role specified, it uses
the default one. Way better, but I still needed to write that &lt;code class="docutils literal"&gt;default-role&lt;/code&gt;
definition at the beginning of each document...&lt;/p&gt;
&lt;p&gt;Finally, I thought of an even better solution: Create an rst reader plugin
for pelican which overrides the default reader only when parsing literals,
in order to output &lt;code class="docutils literal"&gt;&amp;lt;code&amp;gt;&lt;/code&gt; instead of &lt;code class="docutils literal"&gt;&amp;lt;tt&amp;gt;&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;This honestly was easier than I expected. It just took a bit of copying from
pelican's and Sphinx's readers and worked perfectly! Now I could finally write
plain rst literals and have them be converted to &lt;code class="docutils literal"&gt;&amp;lt;code&amp;gt;&lt;/code&gt; tags without any
additional work 🙂. Relevant commits: &lt;a class="reference external" href="https://codeberg.org/nfraprado/blog/commit/5349ef680c32f124cc425699749498f5debda0a1"&gt;1&lt;/a&gt; and &lt;a class="reference external" href="https://codeberg.org/nfraprado/blog/commit/bfb15a96a09ef7dd3403b9ed3e29f89a2a743731"&gt;2&lt;/a&gt; (this is just me updating all
blog texts to use the new syntax).&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="conclusion"&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;With these customizations, I have a blog I'm very comfortable with, both when
writing and reading it. These changes I showed were the ones I thought were the
most interesting, but naturally &lt;a class="reference external" href="https://codeberg.org/nfraprado/blog"&gt;the whole code is open&lt;/a&gt;, so feel free to
check the rest of it.&lt;/p&gt;
&lt;/div&gt;
</content><category term="2021"/><category term="blog"/></entry><entry><title>One year of blog</title><link href="https://nfraprado.net/post/one-year-of-blog.html" rel="alternate"/><published>2021-06-22T00:00:00-03:00</published><updated>2021-06-22T00:00:00-03:00</updated><author><name>Nícolas F. R. A. Prado</name></author><id>tag:nfraprado.net,2021-06-22:/post/one-year-of-blog.html</id><summary type="html">&lt;p&gt;It's been a full year since I started this blog! So I thought I'd take this
chance to talk a bit about the blog itself: How it started and my thoughts on
it.&lt;/p&gt;
&lt;div class="section" id="origins"&gt;
&lt;h2&gt;Origins&lt;/h2&gt;
&lt;p&gt;I had already thought a bit about having my own blog. Having a little corner of …&lt;/p&gt;&lt;/div&gt;</summary><content type="html">&lt;p&gt;It's been a full year since I started this blog! So I thought I'd take this
chance to talk a bit about the blog itself: How it started and my thoughts on
it.&lt;/p&gt;
&lt;div class="section" id="origins"&gt;
&lt;h2&gt;Origins&lt;/h2&gt;
&lt;p&gt;I had already thought a bit about having my own blog. Having a little corner of
the internet where I'd be free to talk about stuff I'm interested in sounded
really cool. And maybe other people would even find it interesting as well!&lt;/p&gt;
&lt;p&gt;But I'm not interested in web development at all, so I just wanted to write my
posts without needing to deal with that. That seems to point to ready-to-use
site generators like WordPress, but I didn't like the sound of that as well.&lt;/p&gt;
&lt;p&gt;Luckily, there was already a trend of using static site generators. They were
fast, simple to use and you could version the source in git and host the site
for free using Gitlab/Github! As soon as I learned about them I knew that was
what I wanted.&lt;/p&gt;
&lt;p&gt;At the time two of my friends had their own blogs already
(&lt;a class="reference external" href="https://rafaelg.net.br"&gt;https://rafaelg.net.br&lt;/a&gt; and &lt;a class="reference external" href="https://andrealmeid.com"&gt;https://andrealmeid.com&lt;/a&gt;) using static site
generators and they encouraged me to create one as well. One of them introduced
me to &lt;a class="reference external" href="https://github.com/getpelican/pelican/"&gt;pelican&lt;/a&gt;, a static blog generator in python and I decided to give it a
try.&lt;/p&gt;
&lt;p&gt;After choosing a theme and a bit of customization in pelican (which I'll cover
in the next post), I was ready to start writing. And that's when I began the
blog with the &lt;a class="reference external" href="/post/creating-movie-and-game-lists-using-taskwarrior.html"&gt;"Creating movie and game lists using Taskwarrior" post&lt;/a&gt;, which is funnily personal, while still
technical. I like this style 🙂.&lt;/p&gt;
&lt;p&gt;So that's how the blog started. Now I want to go a bit further on the principles
that guide the blog.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="principles"&gt;
&lt;h2&gt;Principles&lt;/h2&gt;
&lt;p&gt;There are two things I think are very important about the way my blog works: the
dual-language and the open diary format.&lt;/p&gt;
&lt;div class="section" id="dual-language"&gt;
&lt;h3&gt;Dual-language&lt;/h3&gt;
&lt;p&gt;You might have noticed that all my blog posts have a &amp;quot;translations&amp;quot; field
listing one other language. And there's also a button to change languages on the
navigation bar. That's because my blog is available in both English and
Brazilian Portuguese. To make this possible I translated the interface to
Portuguese and, more importantly, write each and every post in both English and
Portuguese.&lt;/p&gt;
&lt;p&gt;The interface was really easy to translate, but needing to write each post
effectively twice is a lot of extra work. So why do I do it?&lt;/p&gt;
&lt;p&gt;When I was thinking about creating the blog, I faced a dilemma: in which
language should I write in?&lt;/p&gt;
&lt;p&gt;On one hand, writing in English would make my content widely available, since
it is the de facto language of the internet. It would make it possible for
example to share a post with a community I contributed to.&lt;/p&gt;
&lt;p&gt;On the other hand, I like my mother language. And I'd want to be able to share
my posts with people I know personally without an artificial barrier: when both
me and the person are more familiar with Portuguese, it seems nonsensical to
transmit the information in English. I also didn't want to alienate people, from
my own country, which don't speak English (even though that is becoming ever
rarer).&lt;/p&gt;
&lt;p&gt;So I decided to go with both 🙂. Double the work, but none of the downsides and
double the upsides. Actually it's not double the work, since the second post is
mostly a translation of the other one rather than a completely new post. The
fact that I don't post that often (once per month) also helps.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="open-diary-format"&gt;
&lt;h3&gt;Open diary format&lt;/h3&gt;
&lt;p&gt;Another thing is the way I approach my posts, and my motivation. Or the answer
to the question: There are a gazillion blogs out there already, why would anyone
read mine? Is it really worth it to have a blog?&lt;/p&gt;
&lt;p&gt;And my answer to that is: Yes it is, because I'm not writing for others.&lt;/p&gt;
&lt;p&gt;The main reason I write on my blog is because I want to record the projects I
did and stuff I found interesting, so that in the future I can look back to
those. So it's basically a diary (although monthly).&lt;/p&gt;
&lt;p&gt;But of course if anyone is interested in any subject they're free to check the
posts, which makes this more of an open diary. That's very cool if it happens,
but I don't count on it. I write for myself, but if others like it that's a very
nice bonus 🙂.&lt;/p&gt;
&lt;p&gt;This is why I don't tend to do tutorials, and instead document my experience
through learning or building something. It has a much more personal take, even
though it isn't as easily approachable by others.&lt;/p&gt;
&lt;p&gt;(After writing down these two principles I do realize that they're a bit
contradictory, but what can I say, it's how I feel. I am human after all 😛.)&lt;/p&gt;
&lt;p&gt;Since I was treating my blog as a diary it made sense to me to try and write
with a fixed frequency. I ended up deciding on once a month since that was
enough to keep a certain momentum while not as much as to make me stressed over
it.&lt;/p&gt;
&lt;p&gt;I also ended up making the 20th of the month be my target date. This is only a
guideline. I don't force myself to do any of this. If I don't feel like posting
this month or can't make it to the 20th, that's okay. But by creating this
optional deadline there's a healthy pressure to make me win over any laziness
and think if there was anything interesting recently that I want to share (with
my future self and with others).&lt;/p&gt;
&lt;p&gt;This has worked really well so far. I feel like I've always had something
interesting to share, and I did post every single month so far, missing just a
couple times the 20th (this one included 😝). By limiting myself to one post a
month I also manage to create a backlog of potential posts, even though there's
usually something left to do on each of them before they can be posted (I have
at least three like this at the moment).&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="conclusion"&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;So, in summary, it's been a really nice journey so far. I'm happy to have
already accumulated 13 posts (14 with this one) I can look back at and be
nostalgic about stuff I built or learnt about, and maybe even get ideas for
future projects!&lt;/p&gt;
&lt;p&gt;I'm not sure for how long this will keep going, but so far I don't see the end
🙂.&lt;/p&gt;
&lt;p&gt;This was it about the &amp;quot;human&amp;quot; side of the blog. In the next post I'll talk about
the technical side to it: the customizations I did in the blog over this past
year to suit my needs.&lt;/p&gt;
&lt;/div&gt;
</content><category term="2021"/><category term="blog-birthday"/><category term="blog"/></entry><entry><title>Keeping track of my packages</title><link href="https://nfraprado.net/post/keeping-track-of-my-packages.html" rel="alternate"/><published>2021-05-20T00:00:00-03:00</published><updated>2021-05-20T00:00:00-03:00</updated><author><name>Nícolas F. R. A. Prado</name></author><id>tag:nfraprado.net,2021-05-20:/post/keeping-track-of-my-packages.html</id><summary type="html">&lt;p&gt;As I previously mentioned in the &lt;a class="reference external" href="/post/moving-to-wayland.html"&gt;"Moving to Wayland" post&lt;/a&gt;, I recently moved to a
new computer. Moving can be very annoying if you use a heavily configured system
and don't have all the configurations easily available to just move over. Since
I do regular backups of my files, which …&lt;/p&gt;</summary><content type="html">&lt;p&gt;As I previously mentioned in the &lt;a class="reference external" href="/post/moving-to-wayland.html"&gt;"Moving to Wayland" post&lt;/a&gt;, I recently moved to a
new computer. Moving can be very annoying if you use a heavily configured system
and don't have all the configurations easily available to just move over. Since
I do regular backups of my files, which include a &lt;code class="docutils literal"&gt;dotfiles&lt;/code&gt; folder with all
(or almost all) configuration files for the programs I use, I thought it would
be straightforward.&lt;/p&gt;
&lt;p&gt;While moving though, I noticed I didn't have an easy way to see and install all
the programs I use. At that point I started to wonder about using a distro like
&lt;a class="reference external" href="https://nixos.org/"&gt;NixOS&lt;/a&gt; that uses a declarative package manager at its heart precisely to make it
easy to reproduce a system. But I like &lt;a class="reference external" href="https://archlinux.org/"&gt;Arch Linux&lt;/a&gt; a lot, and there honestly
isn't a reason I can't have that feature with it.&lt;/p&gt;
&lt;div class="section" id="declarative-package-management"&gt;
&lt;h2&gt;Declarative package management&lt;/h2&gt;
&lt;p&gt;So the aim was to have declarative package management to make future moves
easier. Also, since then the packages are listed in a file, by versioning it in
my backups I also gain the ability to rewind to an old version for free (if I
want to revert some mistake in the package list, for example).&lt;/p&gt;
&lt;p&gt;But having a simple list of the installed packages isn't the greatest thing
about declarative package management. The greatest benefit is that you can
&lt;em&gt;organize&lt;/em&gt; that list in a way that makes sense to you, by reordering and
grouping the packages. And you can leave comments all over the file, which is
particularly useful to register the reason each package is installed, so that in
the future you can use that information to remove a package when no longer
needed.&lt;/p&gt;
&lt;p&gt;A quick search on the web for declarative package managers for Arch Linux
revealed &lt;a class="reference external" href="https://github.com/CyberShadow/aconfmgr"&gt;aconfmgr&lt;/a&gt; and &lt;a class="reference external" href="https://github.com/rendaw/decpac"&gt;decpac&lt;/a&gt;. The former is more complete, featuring built-in
configuration management as well, but that's overkill for me and it's in bash,
so it's a no-go. The latter is very simple, you basically just have a file with
the commands to install the packages and the packages themselves in a syntax
similar to JSON. And it's written in python.&lt;/p&gt;
&lt;p&gt;decpac also allows you to install from the &lt;a class="reference external" href="https://aur.archlinux.org/"&gt;AUR&lt;/a&gt; by relying on the &lt;a class="reference external" href="https://github.com/Jguer/yay"&gt;yay&lt;/a&gt; tool. The
AUR packages are identified by a simple markup before the package name.&lt;/p&gt;
&lt;p&gt;decpac is &lt;em&gt;almost&lt;/em&gt; perfect, but I didn't like that it did the command
configuration and package listing in the same file, which made it need a more
complex syntax, that is similar to JSON but not quite.&lt;/p&gt;
&lt;p&gt;That's why I decided to create my own, which I called, very creatively, pcmn.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="pcmn"&gt;
&lt;h2&gt;pcmn&lt;/h2&gt;
&lt;p&gt;&lt;a class="reference external" href="https://codeberg.org/nfraprado/pcmn"&gt;pcmn&lt;/a&gt; is really simple, with less than 200 lines of python code. It is heavily
inspired by decpac with the main difference being the simpler syntax. And it is
very simple indeed:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;one package per line (but lines with no packages are allowed as well of
course)&lt;/li&gt;
&lt;li&gt;everything after a &lt;code class="docutils literal"&gt;#&lt;/code&gt; is a comment&lt;/li&gt;
&lt;li&gt;optionally the package's group can be written inside brackets before the
package name. When no group is specified the default is assumed, which
installs from the official repository. The only other group is &lt;code class="docutils literal"&gt;aur&lt;/code&gt;, which
informs the package should be installed from the AUR using yay.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;There are only two commands:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;code class="docutils literal"&gt;generate&lt;/code&gt; creates a new package list from the currently explicitly
installed packages (that is, dependencies won't appear in the list, which is
fine since pacman will resolve them).&lt;/li&gt;
&lt;li&gt;&lt;code class="docutils literal"&gt;apply&lt;/code&gt; applies the package list, that is, packages not listed will be
uninstalled and packages in the list that aren't present will be installed.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;A separate config file in JSON can be used to change the commands that are used
for both the default and the &lt;code class="docutils literal"&gt;aur&lt;/code&gt; group, to list, install and remove the
packages.&lt;/p&gt;
&lt;p&gt;I've been using it for a few months now and I'm really happy with it. As I
mentioned, the main difference from decpac is that I split the configuration and
the package list and I think it makes a world of difference. The package list is
really clean and easy to add to. Here's a sample of mine:&lt;/p&gt;
&lt;pre class="code python literal-block"&gt;
&lt;span class="c1"&gt;#&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="c1"&gt;# Utilities&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="c1"&gt;#&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="c1"&gt;# Backups&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;borg&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;borgmatic&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;python&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;llfuse&lt;/span&gt; &lt;span class="c1"&gt;# Needed to mount an archive&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="c1"&gt;# Notifications&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;libnotify&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;mako&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="c1"&gt;# Keyboard (Custom)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;interception&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;caps2esc&lt;/span&gt; &lt;span class="c1"&gt;# Switch Caps with ESC&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;aur&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="n"&gt;interception&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;space2meta&lt;/span&gt; &lt;span class="c1"&gt;# Use Space as Meta&lt;/span&gt;
&lt;/pre&gt;
&lt;p&gt;Every time I notice I need a new package for something I add it to the list,
together with a comment with the reason why, and run &lt;code class="docutils literal"&gt;pcmn apply&lt;/code&gt; so that it
gets installed. When I need to install a package temporarily just do something
quick or some test, I install it manually using pacman as usual, so that the
next time I run an &lt;code class="docutils literal"&gt;apply&lt;/code&gt; it automatically gets uninstalled.&lt;/p&gt;
&lt;p&gt;If you found pcmn interesting, check out &lt;a class="reference external" href="https://codeberg.org/nfraprado/pcmn"&gt;its repository&lt;/a&gt; for some
more information. It's also easily installable &lt;a class="reference external" href="https://aur.archlinux.org/packages/pcmn-git/"&gt;from the AUR&lt;/a&gt;, which means
that after you &lt;code class="docutils literal"&gt;generate&lt;/code&gt; the package list, &lt;code class="docutils literal"&gt;[aur] pcmn-git&lt;/code&gt; will appear in
its own package list. I find that funny for some reason 😝.&lt;/p&gt;
&lt;p&gt;And that's why I like python so much. With less than 200 lines of code I wrote a
program that solves a real problem I was having. &lt;a class="reference external" href="https://xkcd.com/353/"&gt;And it was fun to write
too!&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;
</content><category term="2021"/><category term="sysadmin"/><category term="python"/></entry><entry><title>Learning music theory by writing melodies</title><link href="https://nfraprado.net/post/learning-music-theory-by-writing-melodies.html" rel="alternate"/><published>2021-04-20T00:00:00-03:00</published><updated>2021-04-20T00:00:00-03:00</updated><author><name>Nícolas F. R. A. Prado</name></author><id>tag:nfraprado.net,2021-04-20:/post/learning-music-theory-by-writing-melodies.html</id><summary type="html">&lt;p&gt;Hey, first non-technical blog post 🙂.&lt;/p&gt;
&lt;p&gt;Anyway, I haven't talked about this before here, but I'm really interested in
playing the piano. When I was younger I took some guitar classes, but I always
wanted to play the piano, and a couple years ago I finally got one! I've been
playing …&lt;/p&gt;</summary><content type="html">&lt;p&gt;Hey, first non-technical blog post 🙂.&lt;/p&gt;
&lt;p&gt;Anyway, I haven't talked about this before here, but I'm really interested in
playing the piano. When I was younger I took some guitar classes, but I always
wanted to play the piano, and a couple years ago I finally got one! I've been
playing it since, although never really took classes and also didn't practice
very hard, but I do have a lot of fun with it.&lt;/p&gt;
&lt;p&gt;What I like the most is improvising. I find it very relaxing, and sometimes
interesting things come up that I want to develop further into a music.&lt;/p&gt;
&lt;p&gt;Another thing I'm really interested in is music theory. I find it fascinating
how we can dissect music to get to their building blocks and find out what makes
them sound good. Also, I think if I learn more music theory I can have even more
fun improvising and composing on the piano 😀.&lt;/p&gt;
&lt;p&gt;Two great resources I'm using right now to learn music theory are:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;a class="reference external" href="https://www.daveconservatoire.org/"&gt;Dave Conservatoire&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference external" href="https://www.coursera.org/learn/melodic-forms-simple-harmony"&gt;&amp;quot;Approaching Music Theory: Melodic Forms and Simple Harmony&amp;quot; Course on
Coursera&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Dave Conservatoire is like the Khan Academy for Music Theory and it's really
good! I'm brushing up on the basic concepts as well as learning new ones there.&lt;/p&gt;
&lt;p&gt;The Coursera course is what I'm most excited about, because it teaches the
building blocks of music in a way that is fun, and by analyzing existing pieces
of music. And even more, there are assignments in which you need to compose your
own short melody in different styles. And this is what I want to talk about in
this post!&lt;/p&gt;
&lt;p&gt;So far through the course I had to make melodies in three different styles:
Gregorian chant, jazz and folk. I tried to compose only by writing on the paper,
but I honestly relied a lot on playing them on the piano to hear how each note
sounded.&lt;/p&gt;
&lt;p&gt;After I finished each melody, I transcribed them to a more professional music
sheet using &lt;a class="reference external" href="https://musescore.org"&gt;Musescore&lt;/a&gt;, which is an awesome open-source software for writing
music sheet. What is even more awesome is that it can play your music sheet
using MIDI! So for each melody I exported both the music sheet and the audio
playback so I can show them both here.&lt;/p&gt;
&lt;div class="section" id="my-melodies"&gt;
&lt;h2&gt;My melodies&lt;/h2&gt;
&lt;div class="section" id="gregorian-chant"&gt;
&lt;h3&gt;Gregorian chant&lt;/h3&gt;
&lt;p&gt;The idea for the Gregorian chant was to do something using a very limited set of
notes, and few leaps, but as you can see I broke this second rule, although I
think it sounds pretty good.&lt;/p&gt;
&lt;p&gt;Being a &lt;a class="reference external" href="https://en.wikipedia.org/wiki/Gregorian_chant"&gt;Gregorian chant&lt;/a&gt;, the sheet should only have 4 lines and not have
measures at all, it should have a natural flow. But Musescore isn't really meant
to do that, so that's why both the audio and the sheet are more &amp;quot;robotic&amp;quot; than
I'd like.&lt;/p&gt;
&lt;p&gt;&lt;a class="reference external" href="https://nfraprado.net/audio/melodies/gregorian_chant.mp3"&gt;Listen here&lt;/a&gt;&lt;/p&gt;
&lt;img alt="Gregorian chant melody music sheet" src="/images/melodies/gregorian_chant.png" /&gt;
&lt;/div&gt;
&lt;div class="section" id="slow-jazz"&gt;
&lt;h3&gt;Slow Jazz&lt;/h3&gt;
&lt;p&gt;Next, in the slow Jazz, there's should be a more strict sense of time, but have
the pitches be more free, so accidentals are even encouraged.&lt;/p&gt;
&lt;p&gt;For the accidentals I used both A and D natural, but as you can see, there
aren't any sharp A's to make the natural ones feel off the key signature, as I
later learned. That's definitely something I'd want to improve next time.&lt;/p&gt;
&lt;p&gt;I also think I accidentally put too much of the aesthetic of Genshin Impact's
soundtrack, which although is pretty, isn't Jazz 😛.&lt;/p&gt;
&lt;p&gt;&lt;a class="reference external" href="https://nfraprado.net/audio/melodies/slow_jazz.mp3"&gt;Listen here&lt;/a&gt;&lt;/p&gt;
&lt;img alt="Slow Jazz melody music sheet" src="/images/melodies/slow_jazz.png" /&gt;
&lt;/div&gt;
&lt;div class="section" id="folk"&gt;
&lt;h3&gt;Folk&lt;/h3&gt;
&lt;p&gt;Finally, the Folk melody should be something more repetitive and easy to
remember and sing along. I did it in pentatonic since I learned that worked
well, and I also think having an interesting rhythm is important, so that's what
I did.&lt;/p&gt;
&lt;p&gt;&lt;a class="reference external" href="https://nfraprado.net/audio/melodies/folk.mp3"&gt;Listen here&lt;/a&gt;&lt;/p&gt;
&lt;img alt="Folk melody music sheet" src="/images/melodies/folk.png" /&gt;
&lt;p&gt;These are all for now, but I hope to share more in the near future 🙂.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
</content><category term="2021"/><category term="music"/><category term="music-theory"/><category term="piano"/></entry><entry><title>Moving to Wayland</title><link href="https://nfraprado.net/post/moving-to-wayland.html" rel="alternate"/><published>2021-03-21T00:00:00-03:00</published><updated>2021-03-21T00:00:00-03:00</updated><author><name>Nícolas F. R. A. Prado</name></author><id>tag:nfraprado.net,2021-03-21:/post/moving-to-wayland.html</id><summary type="html">&lt;p&gt;In the middle of January, my computer decided to surprise me, and not in a good
way. Differently from all the quirks I've come to expect from it after all these
6 years of use — faulty keyboard, flashing screen, bad audio jack — this time it
was worse, and it wasn't …&lt;/p&gt;</summary><content type="html">&lt;p&gt;In the middle of January, my computer decided to surprise me, and not in a good
way. Differently from all the quirks I've come to expect from it after all these
6 years of use — faulty keyboard, flashing screen, bad audio jack — this time it
was worse, and it wasn't even a hardware issue.&lt;/p&gt;
&lt;p&gt;At least once a day, my computer would completely freeze. The only action it
responded to was, yes, holding down the power button to force it to shutdown.
Not great.&lt;/p&gt;
&lt;p&gt;I had the idea to try to SSH in using my phone (thank god for Termux) and that
worked. I listed the processes and saw &lt;code class="docutils literal"&gt;xorg-server&lt;/code&gt; using 100% of CPU. I
killed it, and the computer magically came back to life. But obviously, having
just killed the graphical interface, all my open applications were gone with it,
and I was basically as good as getting back from a reboot anyway.&lt;/p&gt;
&lt;p&gt;After about three days enduring this, trying to check logs to debug it, I was
done with X.Org.&lt;/p&gt;
&lt;p&gt;I'm sure most of you know about it, and I'm certainly not well qualified to talk
about this, but X.Org, the application that has long powered the Linux graphical
interfaces is a giant mess. Its code is very hard to maintain, and its
architecture presents security concerns, to the point that a new alternative was
born quite a few years ago called &lt;a class="reference external" href="https://wayland.freedesktop.org/"&gt;Wayland&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The thing is, a lot of applications today still depend on X, which also means
moving to Wayland can be a breaking change. So although the move to Wayland for
the ecosystem as a whole seems inevitable, people are still confortable enough
with X.Org for the time being.&lt;/p&gt;
&lt;p&gt;That was me as well, until X itself gave me a reason to quit. That issue could
probably be investigated and fixed, by why waste time on &lt;a class="reference external" href="https://www.phoronix.com/scan.php?page=news_item&amp;amp;px=X.Org-Maintenance-Mode-Quickly"&gt;something that will
soon enter maintenance mode&lt;/a&gt;? It was finally time I gave Wayland a shot.&lt;/p&gt;
&lt;div class="section" id="making-the-switch"&gt;
&lt;h2&gt;Making the switch&lt;/h2&gt;
&lt;p&gt;From my point of view, switching to Wayland means essentially installing it and
changing all the applications I used on X, to equivalent ones that work on
Wayland. The main one being the graphical interface itself, which in my case was
the &lt;a class="reference external" href="https://i3wm.org/"&gt;i3&lt;/a&gt; window manager.&lt;/p&gt;
&lt;p&gt;Thankfully, there's an already very well established i3-compatible window
manager for Wayland called &lt;a class="reference external" href="https://swaywm.org/"&gt;sway&lt;/a&gt;, so that change was pretty straight-forward.
It was just a matter of installing sway and changing the config a bit.&lt;/p&gt;
&lt;p&gt;Something that helped things a lot was that the sway wiki has a &lt;a class="reference external" href="https://github.com/swaywm/sway/wiki/i3-Migration-Guide"&gt;i3 migration
guide&lt;/a&gt;, which not only shows the main changes needed in the i3 configuration
for it to work on sway, but also a list of the Wayland programs equivalent to
the ones normally used on X.Org.&lt;/p&gt;
&lt;p&gt;For instance, the keyboard layout setup and toggling, which is normally handled
by setxkbmap in X, is integrated in sway so I just used &lt;code class="docutils literal"&gt;input&lt;/code&gt; commands in
sway's config to configure the possible layouts and to have a binding for
toggling it.&lt;/p&gt;
&lt;p&gt;Still on the keyboard topic, I have grown really accustomed to making my Caps
Lock work double duty: it acts as Esc when just pressed independently, and as
Control when held while pressing another key. I used setxkbmap together with
xcape to achieve this, but those obviously aren't an option on Wayland.&lt;/p&gt;
&lt;p&gt;To my rescue came &lt;a class="reference external" href="https://gitlab.com/interception/linux/tools"&gt;Interception Tools&lt;/a&gt;, which allows the same to be done
regardless of being on X or Wayland. In itself it just provides the framework,
but the function of mapping both Esc and Control to Caps Lock I mentioned can be
easily configured by using the official plugin &lt;a class="reference external" href="https://gitlab.com/interception/linux/plugins/caps2esc"&gt;caps2esc&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Oh, and it can also be combined with another plugin, &lt;a class="reference external" href="https://gitlab.com/interception/linux/plugins/space2meta"&gt;space2meta&lt;/a&gt;, to use the
Spacebar key as space when just pressed, but as the Meta key (the modifier on
i3/sway) when held. I had previously experimented with this in X using xcape but
I kept accidentally triggering bindings, so reverted back. But since space2meta
is smarter and only sends it on the key release event, it's really usable and
worth it for the increased comfort.&lt;/p&gt;
&lt;p&gt;A bit of a side track: If caps2esc and space2meta sound like crazy talk to you,
then I do urge you to try them out. Mainly caps2esc, as that one doesn't have
any drawbacks, honestly. Like, how frequently do you use Caps Lock anyway?
There's no reason to keep it there in the home row. Esc and Control on the other
hand are really useful, even more if you use Vim. Now back on track...&lt;/p&gt;
&lt;p&gt;What about &lt;a class="reference external" href="https://github.com/davatorium/rofi"&gt;rofi&lt;/a&gt;, a menu launcher that I use for all sorts of stuff? It's also X
dependent. There's &lt;a class="reference external" href="https://hg.sr.ht/~scoopta/wofi"&gt;wofi&lt;/a&gt; as an alternative for Wayland, but I didn't really like
it. Felt a lot slower and I'm not a fan of it being GTK (the magnifying glass
icon beside the input field really bothers me). I just want a single color
rectangle with a prompt and options highlighted below, and rofi does that much
better. Luckily some brave soul created a &lt;a class="reference external" href="https://github.com/lbonn/rofi"&gt;rofi fork for Wayland&lt;/a&gt; and it works
like a charm. Mostly...&lt;/p&gt;
&lt;p&gt;Unfortunately, the window switching functionality doesn't work. And I find it
quite useful to go directly to a specific open application, otherwise I need to
cycle through all workspaces searching for it. But it was very easy to find a
window switcher script on the web, although for wofi, so I simply changed it a
bit to work with rofi and got myself &lt;a class="reference external" href="https://gist.github.com/nfraprado/484a0dfe60c49b7d2e9dfb45c3c4a4b5"&gt;a window switcher script&lt;/a&gt;. And that's a
perfectly fine rofi in Wayland.&lt;/p&gt;
&lt;p&gt;Taking screenshots with maim was also no longer possible. The alternative is
&lt;a class="reference external" href="https://github.com/emersion/grim"&gt;grim&lt;/a&gt;, and a separate program, &lt;a class="reference external" href="https://github.com/emersion/slurp"&gt;slurp&lt;/a&gt;, to be able to select a region or a window
for the screenshot. As a nice bonus, as grim's README shows, it's very simple to
make a color picker using it together with slurp and imagemagick, so I set that
up as well. My previous attempt at doing this with maim failed, so this was
a nice bonus of moving to Wayland.&lt;/p&gt;
&lt;p&gt;xclip obviously also doesn't work on Wayland. So to keep my clipboard working, I
needed to install &lt;a class="reference external" href="https://github.com/bugaevc/wl-clipboard"&gt;wl-clipboard&lt;/a&gt;. This wasn't enough for Vim though. In Vim,
operating on the system clipboard is done through the &lt;code class="docutils literal"&gt;+&lt;/code&gt; register, like
&lt;code class="docutils literal"&gt;&amp;quot;+yy&lt;/code&gt; to copy the current line to the clipboard, but that doesn't work with
Wayland's clipboard. I got really close to installing Neovim since it seems to
work there, but I found the &lt;a class="reference external" href="https://github.com/jasonccox/vim-wayland-clipboard"&gt;vim-wayland-clipboard&lt;/a&gt; plugin for vim which works
flawlessly.&lt;/p&gt;
&lt;p&gt;One thing I didn't expect to not work out of the box was screen sharing.
Thankfully &lt;a class="reference external" href="https://wiki.archlinux.org/index.php/PipeWire#WebRTC_screen_sharing"&gt;Pipewire's page on ArchWiki&lt;/a&gt; gave instructions on how to get it
working, which was pretty simple too: just set an environment variable and
install &lt;a class="reference external" href="https://github.com/emersion/xdg-desktop-portal-wlr"&gt;xdg-desktop-portal-wlr&lt;/a&gt; and &lt;code class="docutils literal"&gt;pipewire-media-session&lt;/code&gt;. That said, I did
have an &lt;a class="reference external" href="https://gitlab.freedesktop.org/pipewire/pipewire/-/issues/768"&gt;issue&lt;/a&gt; after a pipeWire update, but it seems to have been solved since.&lt;/p&gt;
&lt;p&gt;I also changed my notification daemon from dunst to &lt;a class="reference external" href="https://github.com/emersion/mako"&gt;mako&lt;/a&gt;. That said, as of
version 1.6.0, dunst supports Wayland as well although it still isn't packaged
on Arch Linux. But I'm fine with mako too to be honest, so will probably stay
with it.&lt;/p&gt;
&lt;p&gt;Some other changes that I don't think deserve any special comment:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;a class="reference external" href="https://github.com/haikarainen/light"&gt;light&lt;/a&gt; instead of xbacklight to control the screen brightness&lt;/li&gt;
&lt;li&gt;&lt;a class="reference external" href="https://gitlab.com/chinstrap/gammastep"&gt;gammastep&lt;/a&gt; instead of redshift to make the screen temperature more comfortable
at night&lt;/li&gt;
&lt;li&gt;&lt;a class="reference external" href="https://github.com/eXeC64/imv"&gt;imv&lt;/a&gt; instead of sxiv as the image viewer&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;And finally, it's important to mention Xwayland. It's the application that
allows you to run X applications on Wayland. It doesn't work for everything
(otherwise I wouldn't have changed all those previous applications), but for
example for Electron applications and for the Steam and games it has worked
flawlessly so far to that point that it's hard to know that they aren't native
on Wayland, which I think is awesome. If Wayland is to be the future, it has to
allow current X applications to continue to run on it until they are properly
ported over to Wayland.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="plot-twist"&gt;
&lt;h2&gt;Plot twist&lt;/h2&gt;
&lt;p&gt;After all that change I was all set up on Wayland, but there were actually some
really annoying graphical glitches. From time to time, applications would start
flashing and glitching and there wasn't anything that made it stop, except, of
course, quitting Wayland. So I was essentially where I started, although a bit
better since in this case the whole computer didn't freeze, so I could at least
save everything before rebooting.&lt;/p&gt;
&lt;p&gt;In the meantime though, I got a new computer from my employer, which was much
appreciated given all the quirks of the old one. Since I had already gone
through all the trouble of the Wayland transition, I installed sway in this new
computer as well. And everything worked perfectly. No issue whatsoever.&lt;/p&gt;
&lt;p&gt;So, what is the conclusion of this story? The crashing/glitching issue was
something related to my old computer's hardware rather than X itself, but at
least that gave me the push I needed to migrate to Wayland. I could have
probably stayed on X on the new computer, yes, but that would only be delaying
the inevitable. The important thing is that now everything works, and I feel
much more comfortable debugging and reporting issues with the graphical
interface, given that it's actively maintained, if that ever happens in the
future.&lt;/p&gt;
&lt;/div&gt;
</content><category term="2021"/><category term="wayland"/></entry><entry><title>Ledsticker: My first holistic SW + HW project</title><link href="https://nfraprado.net/post/ledsticker-my-first-holistic-sw-hw-project.html" rel="alternate"/><published>2021-02-20T00:00:00-03:00</published><updated>2021-02-20T00:00:00-03:00</updated><author><name>Nícolas F. R. A. Prado</name></author><id>tag:nfraprado.net,2021-02-20:/post/ledsticker-my-first-holistic-sw-hw-project.html</id><summary type="html">&lt;p&gt;It was in the middle of 2019. I was taking classes in Embedded Systems
Laboratory and had to make the final project of my choice. I had the idea to do
some sort of a Guitar Hero using a keyboard for the input, an 8x8 LED matrix to
display the …&lt;/p&gt;</summary><content type="html">&lt;p&gt;It was in the middle of 2019. I was taking classes in Embedded Systems
Laboratory and had to make the final project of my choice. I had the idea to do
some sort of a Guitar Hero using a keyboard for the input, an 8x8 LED matrix to
display the notes and speakers to play the note sounds. The problem was that the
university didn't offer the LED matrix or the speakers, so I decided to buy
them, with the excuse that I would want to use them later for my personal
projects as well.&lt;/p&gt;
&lt;p&gt;That project went well, and the end result can be seen on &lt;a class="reference external" href="https://www.youtube.com/watch?v=JIfGUSdEFQ0"&gt;YouTube&lt;/a&gt;. Having
completed it, I started thinking about what to do with the LED matrix.
Eventually, I had an idea I thought was so cool that it just had to be done.&lt;/p&gt;
&lt;p&gt;I enjoyed adding cool stickers to my notebook's lid, and had a lot of them, but
when you get down to it, stickers aren't that interesting. So what if I used the
LED matrix as a sticker? I could glue it to the notebook lid and connect it to
the USB port, so it could get power as well as be controlled. I would be able to
program anything I wanted to be shown on the matrix.&lt;/p&gt;
&lt;div class="section" id="prototyping"&gt;
&lt;h2&gt;Prototyping&lt;/h2&gt;
&lt;p&gt;The first step in turning this into reality was to build a prototype. I already
had the LED matrix module, but it received data from an SPI bus, rather than the
USB that I wanted to use. I started searching the internet for a circuit that
converted USB to SPI and found the MCP2210 IC. I bought some those, but also
bought its development board version, with the IC on a board ready to use, to
make the initial prototyping easier.&lt;/p&gt;
&lt;p&gt;I then searched for software that implemented the interface with MCP2210 and
found &lt;a class="reference external" href="https://github.com/kerrydwong/MCP2210-Library"&gt;MCP2210-Library&lt;/a&gt;. I started writing my own software to initialize the IC
responsible for controlling the LED matrix, MAX7219, (present in the LED matrix
module) by calling the library functions to send the SPI data through USB (which
MCP2210 then forwarded through the SPI bus to MAX7219).&lt;/p&gt;
&lt;p&gt;At this point I had the minimum necessary to verify whether the project was
viable: the necessary hardware components in their own separate ready-to-use
modules, and a minimum software capable of interacting with the ICs and drawing
a fixed pattern on the LED matrix. I connected the USB-to-SPI module to the
computer and to the LED matrix module and ran my program:&lt;/p&gt;
&lt;img alt="USB-to-SPI and LED matrix modules connected. Some LEDs are on showing it's working." src="/images/ledsticker/modules.png" /&gt;
&lt;p&gt;Having confirmed that the project was indeed viable, I started advancing on the
necessary steps to make it usable for the end user. The first thing was to make
it easy to control the LED matrix for the intended purpose. This is when I
decided on the &lt;em&gt;sticker&lt;/em&gt; concept, which is central to the project.&lt;/p&gt;
&lt;div class="section" id="stickers"&gt;
&lt;h3&gt;Stickers&lt;/h3&gt;
&lt;p&gt;I wanted it to be possible to show three different things on the LED matrix:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;A static &amp;quot;image&amp;quot;, that is, turn a static pattern of LEDs on.&lt;/li&gt;
&lt;li&gt;An animated &amp;quot;image&amp;quot;, that is, show a sequence of patterns, each one after
some delay.&lt;/li&gt;
&lt;li&gt;An animated &amp;quot;image&amp;quot; but that was dynamically generated from the output of a
program, so I could show a simulation of the &lt;a class="reference external" href="https://en.wikipedia.org/wiki/Conway%27s_Game_of_Life"&gt;Game of Life&lt;/a&gt;, for example.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;To enable the two first use cases, I defined the concept of a static sticker. It
is simply a text file containing a sequence of commands that determine what is
shown on the matrix. There are commands for turning individual pixels, whole
columns or rows, or the whole screen on or off, for updating the screen, etc.&lt;/p&gt;
&lt;p&gt;To enable the third use case, I defined the dynamic sticker, which is any program,
programmed in any language, that outputs those sticker commands. In this way,
a programming language can be used to generate a more complex and dynamic
sticker.&lt;/p&gt;
&lt;p&gt;So the idea is that the user would execute &lt;code class="docutils literal"&gt;ledsticker&lt;/code&gt; and pass either a
static or dynamic sticker as a parameter. If it was static, the commands would
be read and the LED matrix updated accordingly. Otherwise if it was dynamic, it
would instead be executed and its output used as a static sticker in the same
way.&lt;/p&gt;
&lt;p&gt;For all of this to work, I had to both define all sticker commands, as well as
implement a simple parsing for them in my program. As an example of the
commands, the simplest one is &lt;code class="docutils literal"&gt;on R C&lt;/code&gt;, which turns the LED at row &lt;code class="docutils literal"&gt;R&lt;/code&gt; and
column &lt;code class="docutils literal"&gt;C&lt;/code&gt; on.&lt;/p&gt;
&lt;p&gt;Finally, to make sure those commands were enough, and also for fun, I
implemented some stickers: a single frame with the Creeper face, an animated
Tetris, and a dynamically generated simulation of the Game of Life.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="hardware"&gt;
&lt;h3&gt;Hardware&lt;/h3&gt;
&lt;p&gt;At this point I had the software fully operational, so I wanted to start
tackling the hardware side. I was using separate modules for the LED matrix and
the USB-to-SPI, but the objective really was to have it all in a single small
PCB, in order to be convenient to attach it to the notebook lid.&lt;/p&gt;
&lt;p&gt;But before making the PCB, I had to be sure about the circuit. I had the two
separate modules which were known to work, so I used them as a reference and
read the datasheets for MAX7219 and MCP2210 in order to create the circuit
schematic in &lt;a class="reference external" href="https://kicad.org"&gt;KiCad&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;I ended with a schematic containing the two main ICs, MAX7219 and MCP2210, as
well as all the other passive components needed to make them work (resistors,
capacitors and a crystal), together with the LED matrix and a USB mini
connector.&lt;/p&gt;
&lt;p&gt;The schematic symbol (and footprint) for the LED matrix (model 1088AS) in
particular I had to create myself, since it didn't exist. I followed &lt;a class="reference external" href="https://www.youtube.com/watch?v=vaCVh2SAZY4&amp;amp;list=PLEBQazB0HUyR24ckSZ5u05TZHV9khgA1O"&gt;DigiKey's
Intro to KiCad series&lt;/a&gt; for that.&lt;/p&gt;
&lt;p&gt;With the circuit decided, I soldered all the components and connections in an
universal board (would've been easier on a breadboard, but I don't have a good
one) to test. The most difficult part of this was that the MCP2210 IC is a
Surface Mount Device (SMD), and SMDs aren't meant to be soldered on universal
boards. What I did was to order a SMD adapter PCB with the correct pin sizes so
I could solder the IC on it and solder it on my board. This is what it all
looked like:&lt;/p&gt;
&lt;img alt="Prototype board for Ledsticker. All components and connections are soldered." src="/images/ledsticker/prototype.jpg" /&gt;
&lt;p&gt;After testing this board with some stickers and concluding that it was working
fine, the only final thing to do was the PCB.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="pcb"&gt;
&lt;h2&gt;PCB&lt;/h2&gt;
&lt;p&gt;I had never made a PCB before, and the idea of finally learning it was one of
the things that motivated me to make this project in the first place.&lt;/p&gt;
&lt;p&gt;I learned almost everything from following the &lt;a class="reference external" href="https://www.youtube.com/watch?v=vaCVh2SAZY4&amp;amp;list=PLEBQazB0HUyR24ckSZ5u05TZHV9khgA1O"&gt;DigiKey's Intro to KiCad
series&lt;/a&gt; I already mentioned. There are a lot of different steps in making a
PCB, but they aren't really difficult, just laborious sometimes. Positioning
the components in a compact way while also minding the traces that had to be
made was very tricky, but it was a fun puzzle.&lt;/p&gt;
&lt;p&gt;After finishing the project in KiCad, I ordered the PCB from OSHPark. After 3
long months waiting for it (due to overseas shipping delay due to the pandemic),
the board finally arrived.&lt;/p&gt;
&lt;p&gt;After waiting so long for the board I didn't waste a second to solder. I got all
the components and soldered everything on the same day. Here are all the
components next to the PCB:&lt;/p&gt;
&lt;img alt="Custom PCB for Ledsticker on top. All components that will be soldered on the bottom." src="/images/ledsticker/components_pcb.jpg" /&gt;
&lt;p&gt;Halfway there (or so I thought):&lt;/p&gt;
&lt;img alt="PCB with only some of the components soldered." src="/images/ledsticker/partially_soldered_pcb.jpg" /&gt;
&lt;p&gt;Soldering the SMD components (MCP2210 and USB mini connector) was a lot harder
than the rest. The USB mini in particular was nearly impossible. The pads for
its 5 pins were almost the same size of the leads themselves, and they weren't
very easy to reach with the soldering iron. After multiple tries, I finally got
it though. Here is the final board:&lt;/p&gt;
&lt;img alt="PCB with all components soldered." src="/images/ledsticker/final_board.jpg" /&gt;
&lt;p&gt;Needless to say that it was incredible to see my very own PCB. There's something
magical about holding something you designed yourself, that writing software
could never provide. Seeing the whole circuit tidily organized in this PCB also
felt so much more robust and professional (look at the prototype board again for
comparison...).&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="results"&gt;
&lt;h2&gt;Results&lt;/h2&gt;
&lt;p&gt;With everything finally done, the board, and the program, it was time to play.
The following gif shows both the command line used to load the sticker and the
ledsticker board being updated. Three stickers are loaded one after the other.
First a static Creeper face, then a Tetris animation, and finally a Game of Life
simulation generated from a python program &amp;quot;in real-time&amp;quot; &lt;a class="footnote-reference" href="#footnote-1" id="footnote-reference-1"&gt;[1]&lt;/a&gt;:&lt;/p&gt;
&lt;img alt="GIF showing command line and board with the LEDs updating simultaneously to show three different stickers." src="/images/ledsticker/ledsticker.gif" /&gt;
&lt;p&gt;And this marks the project as complete! Wait, but what about gluing the board to
the notebook lid? Well, when I started this project, I intended to do that due
to a social reason: as I walked with my notebook around the university,
people would ask me about it, so I would have an excuse to geek out about it and
it would be an interesting conversation starter. With the pandemic, I'm always
at home, so doing it would not only be pointless, but then even &lt;em&gt;I&lt;/em&gt; wouldn't see
it. That's why I'm postponing the attachment to after I'm back going to public
spaces again. But just to give an idea of how it would look like, I attached it
using tape just for this picture:&lt;/p&gt;
&lt;img alt="Notebook lid with normal stickers covering most of it and Ledsticker in the middle showing a Creeper face sticker." src="/images/ledsticker/notebook.jpg" /&gt;
&lt;/div&gt;
&lt;div class="section" id="conclusion"&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;This was by far my favorite project I have ever done. It was really a holistic
project, where I needed to design both the software and hardware and think about
so many different aspects. On the software side I had to create commands to
provide a good interface with the user as well as implement the program as a
whole (except for the communication with MCP2210). On the hardware side I
investigated datasheets to design the circuit, arranged it all to be compact and
also created my first PCB!&lt;/p&gt;
&lt;p&gt;If you found the project interesting, I invite you to make your own ledsticker
board and create your own stickers! Everything is open. You can find the command
line program as well as more detailed information on it at &lt;a class="reference external" href="https://codeberg.org/nfraprado/ledsticker"&gt;ledsticker&lt;/a&gt;. The
board schematics together with the hardware description can be found at
&lt;a class="reference external" href="https://codeberg.org/nfraprado/ledsticker-hw"&gt;ledsticker-hw&lt;/a&gt;. You can also &lt;a class="reference external" href="https://oshpark.com/shared_projects/MHbtuh9G"&gt;order the PCB directly from OSHPark&lt;/a&gt; if you want
(I won't receive anything from it). If you do make the board or a sticker, let
me know! 😃 (You can find my email in the About page).&lt;/p&gt;
&lt;img alt="Ledsticker with a Glider sticker. The Glider is walking diagonally on loop." src="/images/ledsticker/glider.gif" /&gt;
&lt;table class="docutils footnote" frame="void" id="footnote-1" rules="none"&gt;
&lt;colgroup&gt;&lt;col class="label" /&gt;&lt;col /&gt;&lt;/colgroup&gt;
&lt;tbody valign="top"&gt;
&lt;tr&gt;&lt;td class="label"&gt;&lt;a class="fn-backref" href="#footnote-reference-1"&gt;[1]&lt;/a&gt;&lt;/td&gt;&lt;td&gt;It isn't really real-time. The dynamic sticker program is run as fast as
it can, filling the input buffer of &lt;code class="docutils literal"&gt;ledsticker&lt;/code&gt;, which is consumed frame
by frame a lot slower, depending on the FPS set.&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/div&gt;
</content><category term="2021"/><category term="project"/><category term="electronics"/><category term="cli"/><category term="c"/></entry><entry><title>Organization beyond Taskwarrior</title><link href="https://nfraprado.net/post/organization-beyond-taskwarrior.html" rel="alternate"/><published>2021-01-20T00:00:00-03:00</published><updated>2021-01-20T00:00:00-03:00</updated><author><name>Nícolas F. R. A. Prado</name></author><id>tag:nfraprado.net,2021-01-20:/post/organization-beyond-taskwarrior.html</id><summary type="html">&lt;p&gt;In the previous article of this series, I went into all my Taskwarrior and VIT
customizations, and my workflow with them, that enables me to organize my tasks
and get them done. Tasks, however, aren't the whole story when getting
organized.&lt;/p&gt;
&lt;p&gt;Another crucial component of organization is having a calendar …&lt;/p&gt;</summary><content type="html">&lt;p&gt;In the previous article of this series, I went into all my Taskwarrior and VIT
customizations, and my workflow with them, that enables me to organize my tasks
and get them done. Tasks, however, aren't the whole story when getting
organized.&lt;/p&gt;
&lt;p&gt;Another crucial component of organization is having a calendar. It enables you
to be aware of time-sensitive tasks and events, and also to make informed
decisions when scheduling new events. Of course this is nothing new, and is even
part of &lt;a class="reference external" href="https://en.wikipedia.org/wiki/Getting_Things_Done"&gt;GTD&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Something that isn't in GTD, though, and that I missed, is something to keep
track of a schedule. GTD is a great system to keep track of all moving parts of
projects in your life, and to get them done, but it isn't at all concerned with
reserving different portions of your day or week to do the basics (like eating),
doing something regularly, or advancing in your general tasks. It just organizes
what to do and &lt;a class="reference external" href="https://nfraprado.net/post/automatic-context-detection-for-taskwarrior.html"&gt;in which contexts&lt;/a&gt;, not precisely when, which may be lacking,
if one is trying to &lt;a class="reference external" href="https://waitbutwhy.com/2016/10/100-blocks-day.html"&gt;use their time well&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;So, besides having VIT as my central tasks organizer, my system also needs&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;a decent calendar, somehow integrated with VIT to also show due dates of
tasks, in addition to normal appointments&lt;/li&gt;
&lt;li&gt;a way to set a schedule, and to be constantly reminded of it&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Let's go over each one.&lt;/p&gt;
&lt;div class="section" id="calendar-using-calcurse"&gt;
&lt;h2&gt;Calendar using calcurse&lt;/h2&gt;
&lt;p&gt;First of all, Taskwarrior actually does have a calendar, which can be shown with
&lt;code class="docutils literal"&gt;task calendar&lt;/code&gt;, but it's honestly useless. You can only see which days have
tasks, but not which tasks those are.&lt;/p&gt;
&lt;p&gt;I wanted a calendar that gave me a good monthly and weekly overview of
appointments, that was lightweight (preferably on the terminal), and
customizable enough to be integrated with VIT. I ended up using &lt;a class="reference external" href="https://www.calcurse.org/"&gt;calcurse&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Now, the big question was: How can I have my tasks show up in
calcurse? Well, since calcurse uses a text file to hold all appointments using a
simple syntax, I just needed a script that reads all tasks from Taskwarrior
and outputs the appointments using calcurse's syntax. And this is the script I
wrote:&lt;/p&gt;
&lt;pre class="code python literal-block"&gt;
&lt;span class="ch"&gt;#!/bin/python&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;tasklib&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;TaskWarrior&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;sys&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="n"&gt;tw&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;TaskWarrior&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'/home/nfraprado/.task/data'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="c1"&gt;# Parse appointments&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;apts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;tw&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;tasks&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'(status:pending or status:waiting or status:completed)'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'cal'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;apt&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;apts&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;    &lt;span class="n"&gt;start&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;apt&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'scheduled'&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="n"&gt;start&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="kc"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;        &lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;stderr&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Apt '&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;apt&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;' has no sched date!&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;        &lt;span class="k"&gt;continue&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;    &lt;span class="n"&gt;summary&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;apt&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="n"&gt;start&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;hour&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;start&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;minute&lt;/span&gt; &lt;span class="o"&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;start_fmt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;start&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;strftime&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;%m/&lt;/span&gt;&lt;span class="si"&gt;%d&lt;/span&gt;&lt;span class="s2"&gt;/%Y&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;        &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;start_fmt&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; [1] &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;summary&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&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="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;        &lt;span class="n"&gt;start_fmt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;start&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;strftime&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;%m/&lt;/span&gt;&lt;span class="si"&gt;%d&lt;/span&gt;&lt;span class="s2"&gt;/%Y &amp;#64; %H:%M&amp;quot;&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="n"&gt;apt&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'due'&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;            &lt;span class="n"&gt;end_fmt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;apt&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'due'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;strftime&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;%m/&lt;/span&gt;&lt;span class="si"&gt;%d&lt;/span&gt;&lt;span class="s2"&gt;/%Y &amp;#64; %H:%M&amp;quot;&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="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;            &lt;span class="n"&gt;end_fmt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;start_fmt&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;        &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;start_fmt&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; -&amp;gt; &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;end_fmt&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;|&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;summary&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="c1"&gt;# Parse due dates for next actions and projects&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;tasks&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;tw&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;tasks&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'(status:pending or status:waiting) and (type:next or '&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;                       &lt;span class="s1"&gt;'type:objective or type:standby)'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;task&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;tasks&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;date_type&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;label&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="p"&gt;[(&lt;/span&gt;&lt;span class="s1"&gt;'due'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;Prazo final: &amp;quot;&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;                             &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'scheduled'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;Prazo inicial: &amp;quot;&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="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;task&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;date_type&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt; &lt;span class="c1"&gt;# Skip tasks with no date&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;            &lt;span class="k"&gt;continue&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;        &lt;span class="n"&gt;start&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;task&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;date_type&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;        &lt;span class="n"&gt;proj&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;Projeto: &amp;quot;&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;task&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'type'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;objective&amp;quot;&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;&amp;quot;&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;        &lt;span class="n"&gt;summary&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;label&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;proj&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;task&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="n"&gt;start&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;hour&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;start&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;minute&lt;/span&gt; &lt;span class="o"&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;start_fmt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;start&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;strftime&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;%m/&lt;/span&gt;&lt;span class="si"&gt;%d&lt;/span&gt;&lt;span class="s2"&gt;/%Y&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;            &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;start_fmt&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; [1] &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;summary&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&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="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;            &lt;span class="n"&gt;start_fmt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;start&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;strftime&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;%m/&lt;/span&gt;&lt;span class="si"&gt;%d&lt;/span&gt;&lt;span class="s2"&gt;/%Y &amp;#64; %H:%M&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;            &lt;span class="n"&gt;end_fmt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;start_fmt&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;            &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;start_fmt&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; -&amp;gt; &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;end_fmt&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;|&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;summary&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;
&lt;p&gt;This script is basically concerned with which tasks will be shown in the
calendar, and in which format. First, there are the &lt;code class="docutils literal"&gt;cal&lt;/code&gt; tasks, which, if you
remember from last post, are my appointments, and they are the main thing that
should be shown in the calendar. Each one of them is converted into an entry
for calcurse, with the &lt;code class="docutils literal"&gt;scheduled&lt;/code&gt; date being used as the start time, and the
&lt;code class="docutils literal"&gt;due&lt;/code&gt; date as the end time. The appointment text is just the plain task
description.&lt;/p&gt;
&lt;p&gt;Then, there are also the normal tasks, which I want to show not as a continued
event in the calendar, but rather I want to have an
entry for the start date, to show me when I can start doing the task, and
another one for the due date, to show me before when it needs to be done. I also
want custom labels prepended to these entries, so I can tell them apart from the
appointments. So this script prepends &amp;quot;Prazo inicial: &amp;quot; to the scheduled date
entry, and &amp;quot;Prazo final: &amp;quot; to the due date entry. Additionally, if the task is
an &lt;code class="docutils literal"&gt;objective&lt;/code&gt; task, &amp;quot;Projeto: &amp;quot; is also added to the description's entry,
meaning the date is relevant for the whole project rather than a single next
action task.&lt;/p&gt;
&lt;p&gt;Initially, that was it. I mapped a key in VIT to run this script, and then
reloaded calcurse's appointments with &lt;code class="docutils literal"&gt;R&lt;/code&gt;. So, I needed two key presses in two
separate windows to see the calendar updated.&lt;/p&gt;
&lt;p&gt;I later discovered that calcurse also supports hooks (like Taskwarrior, as shown
in the previous post), and added a &lt;code class="docutils literal"&gt;pre-load&lt;/code&gt; hook with the following:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;taskwarrior-task2cal&lt;span class="w"&gt; &lt;/span&gt;&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;/home/nfraprado/.calcurse/apts
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Meaning when I type &lt;code class="docutils literal"&gt;R&lt;/code&gt; in calcurse, it automatically runs my script to export
the tasks to calcurse's file, so it is now a single key in calcurse to see my
calendar updated! 🙂&lt;/p&gt;
&lt;p&gt;The following gif shows both a &lt;code class="docutils literal"&gt;next&lt;/code&gt; and a &lt;code class="docutils literal"&gt;cal&lt;/code&gt; task being added in
Taskwarrior and automatically showing up in calcurse:&lt;/p&gt;
&lt;img alt="Tasks being added in Taskwarrior and automatically shown in calcurse" src="/images/org/calcurse_en.gif" /&gt;
&lt;p&gt;&lt;a class="reference external" href="https://asciinema.org/a/385765"&gt;At asciinema.org&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Another little thing is that I have calcurse's &lt;code class="docutils literal"&gt;notification.command&lt;/code&gt;
configured with the following:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;calcurse&lt;span class="w"&gt; &lt;/span&gt;--next&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;sed&lt;span class="w"&gt; &lt;/span&gt;-n&lt;span class="w"&gt; &lt;/span&gt;-e&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;2s/.*\] \(.*\)/\1/p&amp;#39;&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;xargs&lt;span class="w"&gt; &lt;/span&gt;-I&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;{}&amp;#39;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;notify-send&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;  Upcomming appointment&amp;#39;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;{}&amp;#39;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;This makes so that it shows a notification in my system some time (configurable,
I use 10 minutes) before every appointment, with its description.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="schedule-using-python"&gt;
&lt;h2&gt;Schedule using python&lt;/h2&gt;
&lt;p&gt;The first step in maintaining a schedule is, of course, to create it.&lt;/p&gt;
&lt;p&gt;I wanted a simple and easy way to define and later edit my schedule, so I
implemented it using dictionaries in python, with the schedule of each day of
the week being given by a separate dictionary.&lt;/p&gt;
&lt;p&gt;The idea is that the key gives the start time of an action, and the corresponding
value is the action itself. The action is considered the current one from that
time until the time of the next action. For example, I have the following base
schedule dictionary:&lt;/p&gt;
&lt;pre class="code python literal-block"&gt;
&lt;span class="n"&gt;base&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="s1"&gt;'08'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;Banho+café&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;    &lt;span class="s1"&gt;'12'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;Almoçar&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;    &lt;span class="s1"&gt;'15'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;Piano&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;    &lt;span class="s1"&gt;'19'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;Jantar&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;    &lt;span class="s1"&gt;'24'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;Dormir&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/pre&gt;
&lt;p&gt;If this were used as a schedule, it would mean that the schedule starts with
&amp;quot;Banho+café&amp;quot; from 8 in the morning until 12 PM, when it turns into &amp;quot;Almoçar&amp;quot;, and
so on.&lt;/p&gt;
&lt;p&gt;Like any python dictionary, I can then extend it to implement the schedule of a
day, like&lt;/p&gt;
&lt;pre class="code python literal-block"&gt;
&lt;span class="n"&gt;segunda&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;base&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;segunda&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;update&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;    &lt;span class="s1"&gt;'09'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;Tarefas&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/pre&gt;
&lt;p&gt;Now, considering &lt;code class="docutils literal"&gt;segunda&lt;/code&gt; as the schedule, &amp;quot;Banho+café&amp;quot; only goes until 9 AM,
when it turns into &amp;quot;Tarefas&amp;quot;, which in turn goes until 12 PM, when &amp;quot;Almoçar&amp;quot;
starts, like before, and so on.&lt;/p&gt;
&lt;p&gt;A value can also be deleted, like always, using &lt;code class="docutils literal"&gt;del segunda['09']&lt;/code&gt;, for
example.&lt;/p&gt;
&lt;p&gt;To define my weekly schedule I just need to create one dictionary for
each day of the week using specific variable names (&lt;code class="docutils literal"&gt;segunda&lt;/code&gt;, &lt;code class="docutils literal"&gt;terça&lt;/code&gt;,
&lt;code class="docutils literal"&gt;quarta&lt;/code&gt;, &lt;code class="docutils literal"&gt;quinta&lt;/code&gt;, &lt;code class="docutils literal"&gt;sexta&lt;/code&gt;, &lt;code class="docutils literal"&gt;sábado&lt;/code&gt; and &lt;code class="docutils literal"&gt;domingo&lt;/code&gt;).&lt;/p&gt;
&lt;p&gt;I like this system because I can add actions simply adding its name and start
time, and also because I can add common actions to a base dictionary that is
extended by each day's dictionary, reducing redundancy.&lt;/p&gt;
&lt;p&gt;Next, I have a python module that knows how to parse each dictionary to return
the information I'm interested in:&lt;/p&gt;
&lt;pre class="code python literal-block"&gt;
&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;datetime&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;cur_sched&lt;/span&gt;&lt;span class="w"&gt;


&lt;/span&gt;&lt;span class="n"&gt;scheds&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;cur_sched&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;segunda&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;cur_sched&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;terça&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;cur_sched&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;quarta&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;          &lt;span class="n"&gt;cur_sched&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;quinta&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;cur_sched&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sexta&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;cur_sched&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sábado&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;          &lt;span class="n"&gt;cur_sched&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;domingo&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="n"&gt;gran&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;max_minute&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;60&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;gran&lt;/span&gt;&lt;span class="w"&gt;


&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;get_cur_wday_time&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;    &lt;span class="n"&gt;weekday&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;today&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;weekday&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;    &lt;span class="n"&gt;hour&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;today&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;hour&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;    &lt;span class="n"&gt;minute&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;today&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;minute&lt;/span&gt; &lt;span class="o"&gt;//&lt;/span&gt; &lt;span class="n"&gt;gran&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;gran&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;hour&lt;/span&gt; &lt;span class="o"&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;hour&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;24&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;weekday&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;hour&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;minute&lt;/span&gt;&lt;span class="w"&gt;


&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;format_time&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;hour&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;minute&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="n"&gt;minute&lt;/span&gt; &lt;span class="o"&gt;&amp;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;time&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;hour&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;02&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;h&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;minute&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;02&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;    &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;        &lt;span class="n"&gt;time&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;hour&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;02&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="w"&gt;


&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;get_current&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;    &lt;span class="n"&gt;weekday&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;hour&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;minute&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;get_cur_wday_time&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;get_sched&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;weekday&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;hour&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;minute&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;


&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;get_sched&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;weekday&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;hour&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;minute&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;m&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nb"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;minute&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;gran&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;        &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;scheds&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;weekday&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="n"&gt;format_time&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;hour&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;        &lt;span class="k"&gt;except&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;            &lt;span class="k"&gt;pass&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;h&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nb"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;hour&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;m&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nb"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;max_minute&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;gran&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;            &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;                &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;scheds&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;weekday&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="n"&gt;format_time&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;h&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;            &lt;span class="k"&gt;except&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;                &lt;span class="k"&gt;pass&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s1"&gt;''&lt;/span&gt;&lt;span class="w"&gt;


&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;get_new&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;    &lt;span class="n"&gt;weekday&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;hour&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;minute&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;get_cur_wday_time&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;    &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;scheds&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;weekday&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="n"&gt;format_time&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;hour&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;minute&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;    &lt;span class="k"&gt;except&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s1"&gt;''&lt;/span&gt;
&lt;/pre&gt;
&lt;p&gt;&lt;code class="docutils literal"&gt;get_current()&lt;/code&gt; returns the current schedule action based on the current time.
&lt;code class="docutils literal"&gt;get_new()&lt;/code&gt; does the same, but only if the action just started. For example,
if &amp;quot;Piano&amp;quot; goes from 3 to 4 PM, and considering a granularity of 30 minutes
(which I'm currently using), it will return &amp;quot;Piano&amp;quot; only between 3 and 3:30 PM.&lt;/p&gt;
&lt;p&gt;To always be able to easily see what's the current action based on my schedule,
I have an &lt;a class="reference external" href="https://vivien.github.io/i3blocks/"&gt;i3blocks&lt;/a&gt; block in my status bar specific for it:&lt;/p&gt;
&lt;img alt="Status bar showing current schedule action: &amp;quot;Piano&amp;quot;" src="/images/org/i3blocks_sched.png" /&gt;
&lt;p&gt;It just calls &lt;code class="docutils literal"&gt;get_current()&lt;/code&gt; from the previous python module.&lt;/p&gt;
&lt;p&gt;But only knowing the current action isn't enough, I need to be notified when the
current schedule changes. That's why I also have a cron job
running every 30 minutes and calling &lt;code class="docutils literal"&gt;get_new()&lt;/code&gt; to check if the scheduled
action changed and if so, showing me a notification:&lt;/p&gt;
&lt;img alt="Notification showing current schedule action: &amp;quot;Schedule change: Piano&amp;quot;" src="/images/org/schedule_notf.png" /&gt;
&lt;p&gt;Finally, it's also useful to see the weekly schedule as a whole sometimes, so I
have a script that prints it, using a different color for each action in the
schedule, with the colors themselves being random (so they change on every new
run):&lt;/p&gt;
&lt;pre class="code python literal-block"&gt;
&lt;span class="ch"&gt;#!/bin/python&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;schedule&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;colored&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="n"&gt;weekday_name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;2a&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;3a&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;4a&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;5a&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;6a&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;Sáb&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;Dom&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;color&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;      &amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;end&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&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;for&lt;/span&gt; &lt;span class="n"&gt;weekday&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nb"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;7&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;    &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;weekday_name&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;weekday&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;15&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;end&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;''&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt;


&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;get_color&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;    &lt;span class="n"&gt;hex_num&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;hex&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;hash&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;16&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt; &lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;    &lt;span class="n"&gt;hex_num6&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;hex_num&lt;/span&gt;&lt;span class="p"&gt;[:&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;hex_num&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;:]&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;rjust&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'0'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;hex_num6&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'0x'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&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;for&lt;/span&gt; &lt;span class="n"&gt;hour&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nb"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;25&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;minute&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nb"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;schedule&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;max_minute&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;schedule&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;gran&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="n"&gt;hour&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;24&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;minute&lt;/span&gt; &lt;span class="o"&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="k"&gt;break&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;        &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;schedule&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;format_time&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;hour&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;minute&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;5&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot; &amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;end&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&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;for&lt;/span&gt; &lt;span class="n"&gt;weekday&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nb"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;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;text&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;schedule&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get_sched&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;weekday&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;hour&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;minute&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="n"&gt;color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;                &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;colored&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;stylize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;15&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;colored&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;fg&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;get_color&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;))),&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;                      &lt;span class="n"&gt;end&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&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="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;                &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;15&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;end&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;''&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;        &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/pre&gt;
&lt;/div&gt;
&lt;div class="section" id="extra-tasks-on-the-status-bar"&gt;
&lt;h2&gt;Extra: tasks on the status bar&lt;/h2&gt;
&lt;p&gt;Since I already use i3blocks as my system's status bar, I also added a block
with task information to help me to stay on top of my tasks and to regularly
review them (and not only during the weekly review):&lt;/p&gt;
&lt;img alt="Status bar showing the tasks summary" src="/images/org/i3blocks_task.png" /&gt;
&lt;p&gt;The string in the beginning shows by current context, in this case, &lt;code class="docutils literal"&gt;sp&lt;/code&gt;. The
three numbers following are the number of pending &lt;code class="docutils literal"&gt;in&lt;/code&gt; tasks (in yellow), the
number of &amp;quot;stuck&amp;quot; projects (in magenta) and the number of tasks due this week
(in red).&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="future-improvements"&gt;
&lt;h2&gt;Future improvements&lt;/h2&gt;
&lt;p&gt;And that's all there is to my organization system. It's basically VIT on top of
Taskwarrior to organize my tasks, calcurse to show my calendar, and blocks in
the status bar and notifications to help me track and to warn me about my tasks,
schedule and appointments.&lt;/p&gt;
&lt;p&gt;This system serves me well, though there are still things to improve. Mainly
integration with my phone. As I previously noted, this isn't an issue right now
since I'm always home, but as soon as the pandemic is over, I need a good way to
have my tasks on my phone synced with my computer. I need to at least be able to
easily add &lt;code class="docutils literal"&gt;in&lt;/code&gt; tasks, and see all of my reports, optionally with some filter.
I will also need a calendar with the same integration with Taskwarrior I have on
my computer. Perhaps with the whole &lt;a class="reference external" href="https://puri.sm/posts/converging-on-convergence-pureos-is-convergent-welcome-to-the-future/"&gt;&amp;quot;Convergence&amp;quot; theme going on with
Purism&lt;/a&gt;, I may end up buying a Librem 5 and having a similar setup on both
devices 😃.&lt;/p&gt;
&lt;/div&gt;
</content><category term="2021"/><category term="gtd"/><category term="organization"/></entry><entry><title>Managing my tasks using VIT</title><link href="https://nfraprado.net/post/managing-my-tasks-using-vit.html" rel="alternate"/><published>2020-12-22T00:00:00-03:00</published><updated>2020-12-22T00:00:00-03:00</updated><author><name>Nícolas F. R. A. Prado</name></author><id>tag:nfraprado.net,2020-12-22:/post/managing-my-tasks-using-vit.html</id><summary type="html">&lt;p&gt;Two years ago I decided to get more organized about my life. During that time I
read the &lt;a class="reference external" href="https://en.wikipedia.org/wiki/Getting_Things_Done"&gt;Getting Things Done&lt;/a&gt; book and discovered &lt;a class="reference external" href="https://taskwarrior.org/"&gt;Taskwarrior&lt;/a&gt;, a task
manager for the terminal which doesn't get in the way.&lt;/p&gt;
&lt;p&gt;I greatly appreciated Taskwarrior's simplicity and customizability, but after
some time, the need …&lt;/p&gt;</summary><content type="html">&lt;p&gt;Two years ago I decided to get more organized about my life. During that time I
read the &lt;a class="reference external" href="https://en.wikipedia.org/wiki/Getting_Things_Done"&gt;Getting Things Done&lt;/a&gt; book and discovered &lt;a class="reference external" href="https://taskwarrior.org/"&gt;Taskwarrior&lt;/a&gt;, a task
manager for the terminal which doesn't get in the way.&lt;/p&gt;
&lt;p&gt;I greatly appreciated Taskwarrior's simplicity and customizability, but after
some time, the need to write a command for every single action, like &lt;code class="docutils literal"&gt;task
list&lt;/code&gt; to list the tasks, gets tiresome. Even after using aliases to shorten the
commands, like &lt;code class="docutils literal"&gt;tl&lt;/code&gt; for &lt;code class="docutils literal"&gt;task list&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;After searching for a TUI for Taskwarrior, I found &lt;a class="reference external" href="https://github.com/vit-project/vit/tree/2.x"&gt;VIT&lt;/a&gt;. I actually only stuck
with VIT on the second try, because when I first found it, it was written in
Perl and wasn't that great of an interface. But with the rewrite in python by
&lt;a class="reference external" href="https://github.com/thehunmonkgroup"&gt;thehunmonkgroup&lt;/a&gt; and the release of VIT 2.0, it became the perfect interface for
Taskwarrior.&lt;/p&gt;
&lt;p&gt;Before I start explaining my setup, keep in mind that I won't go into detail of
GTD itself, so if you're not familiar with it, maybe take a look at &lt;a class="reference external" href="https://hamberg.no/gtd"&gt;GTD in 15
minutes&lt;/a&gt;. Things will make more sense.&lt;/p&gt;
&lt;div class="section" id="taskwarrior-configuration"&gt;
&lt;h2&gt;Taskwarrior configuration&lt;/h2&gt;
&lt;p&gt;To organize my tasks following the GTD method, I need to add some custom
attributes (called &lt;a class="reference external" href="https://taskwarrior.org/docs/udas.html"&gt;UDAs&lt;/a&gt;) and reports in my Taskwarrior configuration file
(&lt;code class="docutils literal"&gt;.taskrc&lt;/code&gt;).&lt;/p&gt;
&lt;p&gt;My UDA definitions are the following:&lt;/p&gt;
&lt;pre class="code ini literal-block"&gt;
&lt;span class="na"&gt;uda.type.label&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;Type&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="na"&gt;uda.type.values&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;in,next,objective,someday,standby,cal&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="na"&gt;uda.priority.values&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;H,L,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="na"&gt;urgency.uda.priority.H.coefficient&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;6.0&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="na"&gt;urgency.uda.priority.L.coefficient&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;-6.0&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="na"&gt;uda.difficulty.type&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;string&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="na"&gt;uda.difficulty.label&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;Difficulty&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="na"&gt;uda.difficulty.values&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;H,L,&lt;/span&gt;
&lt;/pre&gt;
&lt;p&gt;This adds three different attributes. The first and most important is the
&lt;code class="docutils literal"&gt;type&lt;/code&gt; attribute. I use it to assign the task to one of the main lists defined
in GTD:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;code class="docutils literal"&gt;in&lt;/code&gt; assigns the task to the &amp;quot;In&amp;quot; list, where I first collect my tasks to
take them out of my head.&lt;/li&gt;
&lt;li&gt;&lt;code class="docutils literal"&gt;next&lt;/code&gt; puts it in the &amp;quot;Next actions&amp;quot; list, where the tasks I need to be
doing next live.&lt;/li&gt;
&lt;li&gt;&lt;code class="docutils literal"&gt;objective&lt;/code&gt; assigns it to the &amp;quot;Projects&amp;quot; list, where each task describes
the objective of one of my current projects, and guides the tasks I create
on &amp;quot;Next actions&amp;quot; for each one of the projects.&lt;/li&gt;
&lt;li&gt;&lt;code class="docutils literal"&gt;someday&lt;/code&gt; puts the task in the &amp;quot;Some day/maybe&amp;quot; list. Tasks put there don't
need to be done anytime soon, and may even be uncertain ideas that won't ever
be done at all. Whenever I'm sure I don't want to do it, however, I delete the
task.&lt;/li&gt;
&lt;li&gt;&lt;code class="docutils literal"&gt;standby&lt;/code&gt; assigns it to the &amp;quot;Waiting for&amp;quot; list. That's where the tasks
that depend on the action of others sit.&lt;/li&gt;
&lt;li&gt;&lt;code class="docutils literal"&gt;cal&lt;/code&gt; puts the task in a &amp;quot;Calendar&amp;quot; list, so that it appears on my calendar
with the right date set.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The other attribute is &lt;code class="docutils literal"&gt;priority&lt;/code&gt;, which I use to prioritize tasks. No
priority means medium priority. &lt;code class="docutils literal"&gt;H&lt;/code&gt; means &amp;quot;High&amp;quot; and &lt;code class="docutils literal"&gt;L&lt;/code&gt; means &amp;quot;Low&amp;quot;, and
they increase and decrease the task's urgency, respectively. The &lt;code class="docutils literal"&gt;next&lt;/code&gt; report
sorts tasks by urgency, so setting a high priority makes the task appear higher
on the report, which makes it draw more of my attention as I skim on the report
top to bottom looking for what to do next.&lt;/p&gt;
&lt;p&gt;The last attribute is &lt;code class="docutils literal"&gt;difficulty&lt;/code&gt; which tracks the difficulty of the task,
that is, how much energy it would cost me to complete it. At the time of this
writing, I seldom use it, but the idea is so I can, for example, filter the
&lt;code class="docutils literal"&gt;next&lt;/code&gt; report with only the &lt;code class="docutils literal"&gt;L&lt;/code&gt; &lt;code class="docutils literal"&gt;difficulty&lt;/code&gt; tasks (&lt;em&gt;i.e.&lt;/em&gt;, the easy ones)
whenever I'm tired.&lt;/p&gt;
&lt;p&gt;I also think about adding a &lt;code class="docutils literal"&gt;duration&lt;/code&gt; UDA for tracking, also with &lt;code class="docutils literal"&gt;H&lt;/code&gt; or
&lt;code class="docutils literal"&gt;L&lt;/code&gt;, if I expect a task to take a lot or little time, so I can filter on
however much time I have available at the moment. I still haven't find the need
for it though.&lt;/p&gt;
&lt;p&gt;Then, there are the reports:&lt;/p&gt;
&lt;pre class="code ini literal-block"&gt;
&lt;span class="na"&gt;report.next.columns&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;id,start.age,priority,project,tags,recur,scheduled.countdown,due.relative,until.remaining,description.count,urgency&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="na"&gt;report.next.labels&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;ID,Active,P,Project,Tag,Recur,S,Due,Until,Description,Urg&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="na"&gt;report.next.sort&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;urgency-&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="na"&gt;report.all.columns&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;id,status.short,uuid.short,start.active,entry.age,end.age,type,depends.indicator,priority,project,tags.count,recur.indicator,wait.remaining,scheduled.remaining,due,until.remaining,description&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="na"&gt;report.all.labels&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;ID,St,UUID,A,Age,Done,Type,D,P,Project,Tags,R,Wait,Sch,Due,Until,Description&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="na"&gt;report.all_valid.columns&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;id,status.short,uuid.short,start.active,entry.age,end.age,type,depends.indicator,priority,project,tags.count,recur.indicator,wait.remaining,scheduled.remaining,due,until.remaining,description&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="na"&gt;report.all_valid.labels&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;ID,St,UUID,A,Age,Done,Type,D,P,Project,Tags,R,Wait,Sch,Due,Until,Description&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="na"&gt;report.all_valid.filter&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;(status:pending or status:waiting)&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="na"&gt;report.in.columns&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;id,description&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="na"&gt;report.in.description&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;Inbox&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="na"&gt;report.in.filter&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;status:pending limit:page (type:in)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="na"&gt;report.in.labels&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;ID,Description&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="na"&gt;report.someday.columns&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;id,description.count&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="na"&gt;report.someday.description&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;Someday/Maybe&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="na"&gt;report.someday.filter&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;limit: type:someday status:pending&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="na"&gt;report.someday.labels&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;ID,Description&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="na"&gt;report.standby.columns&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;id,priority,project,due.relative,description.count,urgency&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="na"&gt;report.standby.description&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;WaitingFor&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="na"&gt;report.standby.labels&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;ID,P,Project,Due,Description,Urgency&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="na"&gt;report.standby.filter&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;limit: type:standby status:pending +READY&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="na"&gt;report.standby.sort&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;urgency-&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="na"&gt;report.objectives.columns&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;id,priority,project,description.count,urgency&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="na"&gt;report.objectives.description&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;Projects&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="na"&gt;report.objectives.labels&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;ID,P,Project,Description,Urgency&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="na"&gt;report.objectives.filter&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;limit: type:objective status:pending +UNBLOCKED&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="na"&gt;report.objectives.sort&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;urgency-&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="na"&gt;report.type.columns&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;id,description,type&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="na"&gt;report.type.description&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;Type&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="na"&gt;report.type.filter&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;status:pending limit:page&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="na"&gt;report.type.labels&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;ID,Description,Type&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="na"&gt;report.cal.columns&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;id,entry.age,recur.indicator,scheduled,due,description&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="na"&gt;report.cal.description&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;Calendar&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="na"&gt;report.cal.filter&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;type:cal status:pending limit:page&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="na"&gt;report.cal.labels&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;ID,Age,R,Scheduled,Due,Description&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="na"&gt;report.cal.sort&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;scheduled&lt;/span&gt;
&lt;/pre&gt;
&lt;p&gt;There is a report for each of the aforementioned types so that I can see the
list of tasks of each of them: &lt;code class="docutils literal"&gt;in&lt;/code&gt;, &lt;code class="docutils literal"&gt;next&lt;/code&gt;, &lt;code class="docutils literal"&gt;objectives&lt;/code&gt; (with an 's'),
&lt;code class="docutils literal"&gt;someday&lt;/code&gt;, &lt;code class="docutils literal"&gt;standby&lt;/code&gt; and &lt;code class="docutils literal"&gt;cal&lt;/code&gt;. Additionally, there's the &lt;code class="docutils literal"&gt;all&lt;/code&gt; report
which shows &lt;em&gt;all&lt;/em&gt; the tasks, the &lt;code class="docutils literal"&gt;all_valid&lt;/code&gt; report which is like &lt;code class="docutils literal"&gt;all&lt;/code&gt; but
hides the completed and deleted tasks, and the &lt;code class="docutils literal"&gt;type&lt;/code&gt; report which shows each
of the tasks and its type.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="vit-configuration"&gt;
&lt;h2&gt;VIT configuration&lt;/h2&gt;
&lt;p&gt;If configuring Taskwarrior is all about setting up the attributes and reports to
enable my workflow, configuring VIT is all about setting up bindings to make the
workflow as fluid as possible.&lt;/p&gt;
&lt;p&gt;My bindings (which are set in the &lt;code class="docutils literal"&gt;config.ini&lt;/code&gt; file inside the &lt;code class="docutils literal"&gt;.vit&lt;/code&gt;
folder) are the following:&lt;/p&gt;
&lt;pre class="code ini literal-block"&gt;
&lt;span class="na"&gt;q&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;{ACTION_QUIT}&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="na"&gt;a&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;{ACTION_NOOP}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="na"&gt;aa&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;{ACTION_TASK_ADD}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="na"&gt;ai&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;aatype:in&amp;lt;Space&amp;gt;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="na"&gt;an&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;aatype:next&amp;lt;Space&amp;gt;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="na"&gt;ap&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;aatype:objective project:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="na"&gt;as&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;aatype:someday&amp;lt;Space&amp;gt;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="na"&gt;aw&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;aatype:standby&amp;lt;Space&amp;gt;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="na"&gt;ac&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;aatype:cal schedule:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="na"&gt;aN&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;aatype:next project:{TASK_PROJECT}&amp;lt;Space&amp;gt;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="na"&gt;aP&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;aaproject:{TASK_PROJECT}&amp;lt;Space&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; 

&lt;/span&gt;&lt;span class="na"&gt;gi&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;:in&amp;lt;Enter&amp;gt;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="na"&gt;gn&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;:next&amp;lt;Enter&amp;gt;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="na"&gt;gp&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;:objectives&amp;lt;Enter&amp;gt;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="na"&gt;gs&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;:someday&amp;lt;Enter&amp;gt;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="na"&gt;gw&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;:standby&amp;lt;Enter&amp;gt;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="na"&gt;gc&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;:cal&amp;lt;Enter&amp;gt;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="na"&gt;gl&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;:list&amp;lt;Enter&amp;gt;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="na"&gt;ga&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;:all_valid&amp;lt;Enter&amp;gt;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="na"&gt;gA&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;:all&amp;lt;Enter&amp;gt;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="na"&gt;gP&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;:all_valid project:{TASK_PROJECT}&amp;lt;Enter&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; 

&lt;/span&gt;&lt;span class="na"&gt;M&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;m{TASK_DESCRIPTION}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="na"&gt;S&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;:!r task modify type:someday {TASK_UUID}&amp;lt;Enter&amp;gt;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="na"&gt;W&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;:!r task modify type:standby {TASK_UUID}&amp;lt;Enter&amp;gt;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="na"&gt;P&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;:!r task modify {TASK_UUID} priority:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="na"&gt;F&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;:!r task modify {TASK_UUID} difficulty:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="na"&gt;Y&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;:!r task duplicate {TASK_UUID}&amp;lt;Enter&amp;gt;&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="c1"&gt;#$ = :!r task sync&amp;lt;Enter&amp;gt;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="na"&gt;zp&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;gpf{STUCK_PROJS}&amp;lt;Enter&amp;gt;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="na"&gt;o&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;:! taskopen {TASK_UUID}&amp;lt;Enter&amp;gt;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="na"&gt;C&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;:!r taskwarrior-update_context&amp;lt;Enter&amp;gt;&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="c1"&gt;# Convenience mappings&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="na"&gt;1&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;:1&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="na"&gt;2&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;:2&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="na"&gt;3&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;:3&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="na"&gt;4&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;:4&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="na"&gt;5&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;:5&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="na"&gt;6&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;:6&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="na"&gt;7&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;:7&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="na"&gt;8&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;:8&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="na"&gt;9&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;:9&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="na"&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="s"&gt;m-&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="na"&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="s"&gt;m+&lt;/span&gt;
&lt;/pre&gt;
&lt;p&gt;The main bindings are the ones starting with &lt;code class="docutils literal"&gt;a&lt;/code&gt; or &lt;code class="docutils literal"&gt;g&lt;/code&gt;. Those are the ones
I use all day. The ones starting with &lt;code class="docutils literal"&gt;a&lt;/code&gt; are for adding tasks, and there's
one for each task type, so I can quickly add a task of any type. The ones
starting with &lt;code class="docutils literal"&gt;g&lt;/code&gt; are for going to a report, so I can also quickly jump to a
specific report and see the tasks of that type.&lt;/p&gt;
&lt;p&gt;Some of those are a bit special though. For example, &lt;code class="docutils literal"&gt;aN&lt;/code&gt; adds a task of type
&lt;code class="docutils literal"&gt;next&lt;/code&gt; and with the project of the currently selected task. I use this when
I'm reviewing my current projects on the &lt;code class="docutils literal"&gt;objectives&lt;/code&gt; report and want to add a
task for the next action of the project I'm currently selecting.&lt;/p&gt;
&lt;p&gt;&lt;code class="docutils literal"&gt;gP&lt;/code&gt; is another one I use a lot. It shows all tasks with the same project of
the currently selected task. I use this when I'm looking at a task from a
project and want to see all other tasks of that project, like the objective
given by the &lt;code class="docutils literal"&gt;objective&lt;/code&gt; task, what are the &lt;code class="docutils literal"&gt;next&lt;/code&gt; tasks for it, if there
are things waiting on other people at &lt;code class="docutils literal"&gt;standby&lt;/code&gt; or something marked on the
calendar at &lt;code class="docutils literal"&gt;cal&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Then there are some utility bindings used less often. &lt;code class="docutils literal"&gt;M&lt;/code&gt; edits the selected
task description. &lt;code class="docutils literal"&gt;S&lt;/code&gt; and &lt;code class="docutils literal"&gt;W&lt;/code&gt; change the selected task's type to &lt;code class="docutils literal"&gt;someday&lt;/code&gt;
and &lt;code class="docutils literal"&gt;standby&lt;/code&gt;, respectively. The former is useful for when I decide a task
should be done in the future rather than now, while the latter isn't used much.
&lt;code class="docutils literal"&gt;P&lt;/code&gt; edits the task's priority, while &lt;code class="docutils literal"&gt;F&lt;/code&gt; edits the task's difficulty. &lt;code class="docutils literal"&gt;Y&lt;/code&gt;
duplicates the selected task.&lt;/p&gt;
&lt;p&gt;The &lt;code class="docutils literal"&gt;$&lt;/code&gt; that is commented out is used to sync the tasks to a central
Taskwarrior server using Taskserver. It was essential to keep the tasks
synchronized between my computer and my phone when going out. But since going
out hasn't been a common theme recently, meaning I'm always on my computer, I
disabled it for the time being.&lt;/p&gt;
&lt;p&gt;The &lt;code class="docutils literal"&gt;zp&lt;/code&gt; binding is one that is a bit more complicated but &lt;em&gt;very&lt;/em&gt; handy. It
goes to the &lt;code class="docutils literal"&gt;objectives&lt;/code&gt; report, that shows my current projects, and filters
so that only the projects with &lt;em&gt;no&lt;/em&gt; &lt;code class="docutils literal"&gt;next&lt;/code&gt; tasks are shown.  It is important
with GTD to always make sure that all your projects have next actions assigned
to them, so that the next step in advancing them is obvious.  With this binding,
during my weekly review of all the tasks, I can easily see the projects with no
next actions and then create one for each project using the &lt;code class="docutils literal"&gt;aN&lt;/code&gt; already
shown. If you're wondering what &lt;code class="docutils literal"&gt;{STUCK_PROJS}&lt;/code&gt; means, don't worry, I'll
explain it shortly.&lt;/p&gt;
&lt;p&gt;&lt;code class="docutils literal"&gt;o&lt;/code&gt; uses &lt;a class="reference external" href="https://github.com/jschlatow/taskopen"&gt;taskopen&lt;/a&gt; on the currently selected task, and this is another one I
use all the time. What taskopen does is read through the task's annotations and
open one of them (asking which one, if there are multiple options). If the
annotation is an URL, it will be open on the web browser. If it is the string
&amp;quot;Notes&amp;quot;, taskopen will open the text file associated with that task (or create
one if this is the first time) where you can write longer annotations. These two
are the ones I know and that I use all the time.  Normal text annotations are
ignored by taskopen.&lt;/p&gt;
&lt;p&gt;The &lt;code class="docutils literal"&gt;C&lt;/code&gt; binding runs a script to update the current Taskwarrior context, but I
normally don't need to run it since it runs automatically as explained in the
&lt;a class="reference external" href="/post/automatic-context-detection-for-taskwarrior.html"&gt;"Automatic context detection for Taskwarrior" post&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Finally, I have some convenience mappings. Each of the digits maps to &lt;code class="docutils literal"&gt;:&lt;/code&gt;
followed by that digit, so I can jump to a task with one less keystroke.  In
more detail, normally to jump to task 42 I would need to type &lt;code class="docutils literal"&gt;:&lt;/code&gt;, &lt;code class="docutils literal"&gt;4&lt;/code&gt;,
&lt;code class="docutils literal"&gt;2&lt;/code&gt; and &lt;code class="docutils literal"&gt;&amp;lt;Enter&amp;gt;&lt;/code&gt;. With this binding I can skip the &lt;code class="docutils literal"&gt;:&lt;/code&gt;, so I type just
&lt;code class="docutils literal"&gt;4&lt;/code&gt;, &lt;code class="docutils literal"&gt;2&lt;/code&gt; and &lt;code class="docutils literal"&gt;&amp;lt;Enter&amp;gt;&lt;/code&gt;.  Since it is very common to jump to tasks in VIT,
this one less keystroke pays off.  Additionally, &lt;code class="docutils literal"&gt;-&lt;/code&gt; and &lt;code class="docutils literal"&gt;+&lt;/code&gt; map to &lt;code class="docutils literal"&gt;m-&lt;/code&gt;
and &lt;code class="docutils literal"&gt;m+&lt;/code&gt;, respectively, where &lt;code class="docutils literal"&gt;m&lt;/code&gt; is the default command to modify the task,
so to add a tag to a task I just press &lt;code class="docutils literal"&gt;+&lt;/code&gt;, type the tag name and press
&lt;code class="docutils literal"&gt;&amp;lt;Enter&amp;gt;&lt;/code&gt;.&lt;/p&gt;
&lt;div class="section" id="vit-variable-replacements"&gt;
&lt;h3&gt;VIT variable replacements&lt;/h3&gt;
&lt;p&gt;You may have noticed some &lt;code class="docutils literal"&gt;{SOMETHING}&lt;/code&gt; in the VIT bindings above. I just
wanted to give a short explanation on those (since a full explanation should be
read in VIT's documentation) and also show my custom variable replacement.&lt;/p&gt;
&lt;p&gt;First things first, the &lt;code class="docutils literal"&gt;{ACTION_QUIT}&lt;/code&gt; in the &lt;code class="docutils literal"&gt;q&lt;/code&gt; binding isn't even a
variable replacement, although the syntax is similar (the difference being that
it is the only thing after the &lt;code class="docutils literal"&gt;=&lt;/code&gt;). That's just one of VIT's actions that can
be mapped.  A variable replacement occurs in the &lt;code class="docutils literal"&gt;aN&lt;/code&gt; binding for example:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="na"&gt;aN&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;aatype:next project:{TASK_PROJECT}&amp;lt;Space&amp;gt;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Here, &lt;code class="docutils literal"&gt;{TASK_PROJECT}&lt;/code&gt; will be substituted by the project attribute of the
currently selected task. So that's why that binding does what it does. The
&lt;code class="docutils literal"&gt;aa&lt;/code&gt; in the beginning is mapped to the action to add a new task, then the type
is set to &lt;code class="docutils literal"&gt;next&lt;/code&gt; and the project to the selected task's project. All
&lt;code class="docutils literal"&gt;{TASK_*}&lt;/code&gt; are built-in VIT variable replacements, and can be used for any
task attribute (including UDAs).&lt;/p&gt;
&lt;p&gt;Now, in the case of the &lt;code class="docutils literal"&gt;zp&lt;/code&gt; binding:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="na"&gt;zp&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;gpf{STUCK_PROJS}&amp;lt;Enter&amp;gt;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;code class="docutils literal"&gt;{STUCK_PROJS}&lt;/code&gt; is a custom variable replacement that I created. It was very
simple, I just followed &lt;a class="reference external" href="https://github.com/vit-project/vit/blob/2.x/CUSTOMIZE.md#to-provide-your-own-custom-variable-replacements"&gt;VIT's CUSTOMIZE.md&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Inside my &lt;code class="docutils literal"&gt;.vit&lt;/code&gt; folder I added a &lt;code class="docutils literal"&gt;keybinding/keybinding.py&lt;/code&gt; with the
following:&lt;/p&gt;
&lt;pre class="code python literal-block"&gt;
&lt;span class="kn"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;task_proj_stuck&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;get_stuck_proj_ids&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="k"&gt;class&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nc"&gt;Keybinding&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;    &lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;replacements&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;        &lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;_custom_match&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;variable&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="n"&gt;variable&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'STUCK_PROJS'&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;                &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;variable&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;        &lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;_custom_replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;task&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;arg&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="n"&gt;arg&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s1"&gt;'STUCK_PROJS'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;                &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s1"&gt;' '&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;get_stuck_proj_ids&lt;/span&gt;&lt;span class="p"&gt;()))&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="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="s1"&gt;'match_callback'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;_custom_match&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;                &lt;span class="s1"&gt;'replacement_callback'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;_custom_replace&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;            &lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;        &lt;span class="p"&gt;]&lt;/span&gt;
&lt;/pre&gt;
&lt;p&gt;I then have a &lt;code class="docutils literal"&gt;task_proj_stuck&lt;/code&gt; python module with the following:&lt;/p&gt;
&lt;pre class="code python literal-block"&gt;
&lt;span class="kn"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;tasklib&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;TaskWarrior&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="n"&gt;tw&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;TaskWarrior&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;get_stuck_projects&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="sd"&gt;&amp;quot;&amp;quot;&amp;quot; Get taskwarrior projects that don't have any next actions assigned to
    them.  Next actions here mean actions of type 'next', 'standby' or 'cal',
    either pending or waiting. &amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;    &lt;span class="n"&gt;projects&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;tw&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;tasks&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pending&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'+READY'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'objective'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;    &lt;span class="n"&gt;next_tasks&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;tw&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;tasks&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'(status:pending or status:waiting) and type:next'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;    &lt;span class="n"&gt;standby_tasks&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;tw&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;tasks&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'(status:pending or status:waiting) and type:standby'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;    &lt;span class="n"&gt;cal_tasks&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;tw&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;tasks&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'(status:pending or status:waiting) and type:cal'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;project&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;projects&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;        &lt;span class="n"&gt;count_next&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;next_tasks&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;project&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;project&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'project'&lt;/span&gt;&lt;span class="p"&gt;]))&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;        &lt;span class="n"&gt;count_standby&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;standby_tasks&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;project&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;project&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'project'&lt;/span&gt;&lt;span class="p"&gt;]))&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;        &lt;span class="n"&gt;count_cal&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cal_tasks&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;project&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;project&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'project'&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="n"&gt;count_next&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;count_standby&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;count_cal&lt;/span&gt; &lt;span class="o"&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="k"&gt;yield&lt;/span&gt; &lt;span class="n"&gt;project&lt;/span&gt;&lt;span class="w"&gt;


&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;get_stuck_proj_ids&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;project&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'id'&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;project&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;get_stuck_projects&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;/pre&gt;
&lt;p&gt;So what happens is that the &lt;code class="docutils literal"&gt;get_stuck_proj_ids()&lt;/code&gt; function returns a
generator containing the id of each &lt;code class="docutils literal"&gt;objective&lt;/code&gt; task whose project doesn't
have any &lt;code class="docutils literal"&gt;next&lt;/code&gt; tasks. The &lt;code class="docutils literal"&gt;{STUCK_PROJS}&lt;/code&gt; custom variable replacement then
just calls this function and joins the ids with space.&lt;/p&gt;
&lt;p&gt;For example, suppose there's project &lt;code class="docutils literal"&gt;clean-bedroom&lt;/code&gt; and project
&lt;code class="docutils literal"&gt;write-vit-post&lt;/code&gt;. Project &lt;code class="docutils literal"&gt;clean-bedroom&lt;/code&gt;'s &lt;code class="docutils literal"&gt;objective&lt;/code&gt; task has id 42 and
project &lt;code class="docutils literal"&gt;write-vit-post&lt;/code&gt;'s &lt;code class="docutils literal"&gt;objective&lt;/code&gt; task has id 99. Both of these
projects don't have any &lt;code class="docutils literal"&gt;next&lt;/code&gt; tasks, while all other projects have. Then, by
pressing &lt;code class="docutils literal"&gt;zp&lt;/code&gt;, VIT will execute &lt;code class="docutils literal"&gt;gpf42 99&amp;lt;Enter&amp;gt;&lt;/code&gt;, which goes to the
&lt;code class="docutils literal"&gt;objectives&lt;/code&gt; report and filters for just tasks 42 and 99, so I can focus on
adding next tasks for each one of these stuck projects with &lt;code class="docutils literal"&gt;aN&lt;/code&gt;. Pretty
convenient, right?&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="hooks-and-taskpirate"&gt;
&lt;h2&gt;Hooks and taskpirate&lt;/h2&gt;
&lt;p&gt;Another powerful feature of Taskwarrior that enables extensibility is the &lt;a class="reference external" href="https://taskwarrior.org/docs/hooks.html"&gt;hooks
API&lt;/a&gt;. If you use Git, you may already be familiar with this concept. It enables
a custom script to be run when a certain event occurs in the program, in this
case, in Taskwarrior.&lt;/p&gt;
&lt;p&gt;Instead of just creating a Taskwarrior hook directly, I decided to use
&lt;a class="reference external" href="https://github.com/tbabej/taskpirate"&gt;taskpirate&lt;/a&gt;, which makes the tasks more directly accessible in python. And as
you may already know, &lt;a class="reference external" href="https://nfraprado.net/tag/python.html"&gt;I like python&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;I currently have a single hook named &lt;code class="docutils literal"&gt;pirate_add_inherit.py&lt;/code&gt;, inside the
&lt;code class="docutils literal"&gt;hooks&lt;/code&gt; folder, and what it does is to make certain attributes inheritable
from the &lt;code class="docutils literal"&gt;objectives&lt;/code&gt; task of a project. The code is the following:&lt;/p&gt;
&lt;pre class="code python literal-block"&gt;
&lt;span class="ch"&gt;#!/bin/python&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;tasklib&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;TaskWarrior&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;hook_inherit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;task&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="n"&gt;task&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'project'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;task&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'type'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="s1"&gt;'objective'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;        &lt;span class="n"&gt;tw&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;TaskWarrior&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'/home/nfraprado/.task/data'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;        &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;            &lt;span class="n"&gt;obj_task&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;tw&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;tasks&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'objective'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;project&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;task&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'project'&lt;/span&gt;&lt;span class="p"&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="k"&gt;except&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;            &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;field&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'due'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'priority'&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="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;task&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;field&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;obj_task&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;field&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;                &lt;span class="n"&gt;task&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;field&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;obj_task&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;field&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/pre&gt;
&lt;p&gt;Since it is an &lt;code class="docutils literal"&gt;add&lt;/code&gt; hook, it executes every time a new task is created. What
it does is the following: whenever a task is created with a project, its &lt;code class="docutils literal"&gt;due&lt;/code&gt;
and &lt;code class="docutils literal"&gt;priority&lt;/code&gt; attributes are set equal to those attributes on the
&lt;code class="docutils literal"&gt;objective&lt;/code&gt; task of the project, except if those attributes are explicitly set
in the new task.  This is very useful since then I can set the priority and due
date of a project on the &lt;code class="docutils literal"&gt;objective&lt;/code&gt; task and all tasks of that project will
have the same priority and due date automatically.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="demonstration"&gt;
&lt;h2&gt;Demonstration&lt;/h2&gt;
&lt;p&gt;After talking so much, I owe you at least some gifs showing how this all works
out. It's worth saying the following aren't my real tasks, otherwise you'd be
looking at more than a hundred on the &lt;code class="docutils literal"&gt;someday&lt;/code&gt; report, for example.&lt;/p&gt;
&lt;p&gt;In this first gif, I jump to each report (&lt;code class="docutils literal"&gt;next&lt;/code&gt;, &lt;code class="docutils literal"&gt;in&lt;/code&gt;, &lt;code class="docutils literal"&gt;standby&lt;/code&gt;,
&lt;code class="docutils literal"&gt;objectives&lt;/code&gt; and &lt;code class="docutils literal"&gt;someday&lt;/code&gt;) using the &lt;code class="docutils literal"&gt;g&lt;/code&gt; bindings, then add an &lt;code class="docutils literal"&gt;in&lt;/code&gt;
task with &lt;code class="docutils literal"&gt;ai&lt;/code&gt; and use &lt;code class="docutils literal"&gt;S&lt;/code&gt; to move it to &lt;code class="docutils literal"&gt;someday&lt;/code&gt;. I also use the
&lt;code class="docutils literal"&gt;&amp;lt;Enter&amp;gt;&lt;/code&gt; default binding to inspect the task. You can also see me jumping to
tasks using their id and searching for a string using the default binding &lt;code class="docutils literal"&gt;/&lt;/code&gt;.&lt;/p&gt;
&lt;img alt="VIT navigation and task creation using my custom bindings" src="/images/vit/vit1.gif" /&gt;
&lt;p&gt;&lt;a class="reference external" href="https://asciinema.org/a/380555"&gt;At asciinema.org&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;In this second gif, I use the default binding &lt;code class="docutils literal"&gt;A&lt;/code&gt; to annotate the task with
simple text and then with the string &amp;quot;Notes&amp;quot;. I then use &lt;code class="docutils literal"&gt;o&lt;/code&gt; to make taskopen
open a note for the task where I input more annotations.&lt;/p&gt;
&lt;img alt="Task annotation and usage of taskopen in VIT" src="/images/vit/vit2.gif" /&gt;
&lt;p&gt;&lt;a class="reference external" href="https://asciinema.org/a/380556"&gt;At asciinema.org&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;In this last gif, I use the &lt;code class="docutils literal"&gt;zp&lt;/code&gt; binding to show only the stuck projects, then
use &lt;code class="docutils literal"&gt;aN&lt;/code&gt; on two of them to create &lt;code class="docutils literal"&gt;next&lt;/code&gt; tasks. Finally, I use &lt;code class="docutils literal"&gt;gP&lt;/code&gt; on a
task to show only tasks of its project. Here you can also see the hook in
effect, since the &lt;code class="docutils literal"&gt;Next action 1&lt;/code&gt; task has the same &lt;code class="docutils literal"&gt;priority&lt;/code&gt; and &lt;code class="docutils literal"&gt;due&lt;/code&gt;
attributes as the &lt;code class="docutils literal"&gt;New Project 1&lt;/code&gt; task, even though I didn't specify them.&lt;/p&gt;
&lt;img alt="Usage of my custom zp, aN and gP bindings to ease project tracking in VIT" src="/images/vit/vit3.gif" /&gt;
&lt;p&gt;&lt;a class="reference external" href="https://asciinema.org/a/380557"&gt;At asciinema.org&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;And that's it. If this interested you, take a look at VIT. I only showed my
specific bindings and workflow with it, but VIT is capable of a lot more.&lt;/p&gt;
&lt;p&gt;Lastly, this post only covered the things related to the managing of tasks and
VIT. There are still other very important parts of my organization left to be
explained, like how I see my calendar and how I stay on schedule. I'll go over
these on the next post.&lt;/p&gt;
&lt;/div&gt;
</content><category term="2020"/><category term="vit"/><category term="taskwarrior"/><category term="gtd"/><category term="organization"/></entry><entry><title>Porting a flash LED driver upstream</title><link href="https://nfraprado.net/post/porting-a-flash-led-driver-upstream.html" rel="alternate"/><published>2020-11-20T00:00:00-03:00</published><updated>2020-11-20T00:00:00-03:00</updated><author><name>Nícolas F. R. A. Prado</name></author><id>tag:nfraprado.net,2020-11-20:/post/porting-a-flash-led-driver-upstream.html</id><summary type="html">&lt;p&gt;Now that I had a working serial cable for my Nexus 5, as described in the
&lt;a class="reference external" href="/post/making-an-uart-cable-for-the-nexus-5.html"&gt;"Making an UART cable for the Nexus 5" post&lt;/a&gt;, I was ready to face some action and help in
upstreaming.&lt;/p&gt;
&lt;p&gt;Looking through &lt;a class="reference external" href="https://masneyb.github.io/nexus-5-upstream/TODO.html"&gt;Brian Masney's TODO page&lt;/a&gt; there were a couple options, but the
one …&lt;/p&gt;</summary><content type="html">&lt;p&gt;Now that I had a working serial cable for my Nexus 5, as described in the
&lt;a class="reference external" href="/post/making-an-uart-cable-for-the-nexus-5.html"&gt;"Making an UART cable for the Nexus 5" post&lt;/a&gt;, I was ready to face some action and help in
upstreaming.&lt;/p&gt;
&lt;p&gt;Looking through &lt;a class="reference external" href="https://masneyb.github.io/nexus-5-upstream/TODO.html"&gt;Brian Masney's TODO page&lt;/a&gt; there were a couple options, but the
one I ended going for was the rear flashlight. Hardware doesn't get much simpler
than an LED and it would be easy to test if it was working.&lt;/p&gt;
&lt;p&gt;To my surprise though, &lt;a class="reference external" href="https://github.com/AICP/kernel_lge_hammerhead/blob/n7.1/drivers/leds/leds-qpnp.c"&gt;the downstream driver&lt;/a&gt; had over 3500 lines! But it
wasn't only for the flash LED, it supported multiple LED types: WLED,
Flash/Torch, RGB, MPP and KPDBL. To make it easier to port and since the flash
would be the only one I would be able to test and be sure that worked, I decided
to create a new file for the driver and only copy over what was needed for the
flash LED.&lt;/p&gt;
&lt;p&gt;I started by copying the &lt;em&gt;probe&lt;/em&gt; function, compiling the driver, and seeing
which errors of missing definitions were thrown. If those definitions had
&amp;quot;flash&amp;quot; or &amp;quot;torch&amp;quot; in the name, I would also copy them, otherwise I just removed
those references. I repeated this until eventually there were no more missing
definition errors and my driver had everything needed for the flash LED.&lt;/p&gt;
&lt;p&gt;At this point I still had a lot of compiling errors though, since the downstream
driver was for kernel 3.4 and I was compiling for 5.7.6. So I just went through
each error, referencing the definitions on both the downstream and upstream
kernels, and made the necessary changes.&lt;/p&gt;
&lt;p&gt;With the driver successfully compiling, I added a CONFIG for it and enabled it
as a module in the &lt;em&gt;defconfig&lt;/em&gt; used by Nexus 5 (&lt;code class="docutils literal"&gt;qcom_defconfig&lt;/code&gt;). I also went
through the &lt;em&gt;devicetree&lt;/em&gt; files on the downstream tree to find which nodes I
needed to describe the flash LED hardware to the driver, and the properties
needed in them, so I could also add them upstream.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Note&lt;/strong&gt;: The design of &lt;em&gt;devicetrees&lt;/em&gt; can make the properties of a node be
scattered across different files. Later I learned that I could compile the
downstream kernel and read the source for the complete tree from the blob with
&lt;code class="docutils literal"&gt;dtc -I dtb -O dts -o downstream_dt.dts
kernel_lge_hammerhead/arch/arm/boot/msm8974-hammerhead-rev-11.dtb&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;With a driver that compiles, a valid &lt;em&gt;devicetree&lt;/em&gt;, and configs enabling the
driver as a module, I was ready to finally compile my kernel with the driver and
flash it to the phone. So I &lt;a class="reference external" href="https://codeberg.org/nfraprado/linux/commit/d18087e294bb176ee3ffa94f6f82dc60f4b65b63"&gt;commited my changes&lt;/a&gt; and went to battle.&lt;/p&gt;
&lt;p&gt;And of course it failed. In fact, it failed so badly that the driver wasn't even
binding to the device. Not having a solid understanding of &lt;em&gt;devicetrees&lt;/em&gt; and how
the binding between the devices and drivers worked, I started researching about
it.&lt;/p&gt;
&lt;p&gt;A great resource I found was &lt;a class="reference external" href="https://elinux.org/images/0/04/Dt_debugging_elce_2015_151006_0421.pdf"&gt;Solving Device Tree Issues&lt;/a&gt; (&lt;a class="reference external" href="https://elinux.org/Device_Tree_frowand"&gt;more on eLinux&lt;/a&gt;).
It was in fact using the &lt;code class="docutils literal"&gt;dt_node_info&lt;/code&gt; script that presentation shows that I
found out that the device was being loaded but the driver wasn't. Also, the
debugging techniques shown, like enabling the dynamic debug prints on &lt;em&gt;probe&lt;/em&gt;
functions helped me discover that my driver's &lt;em&gt;probe&lt;/em&gt; function wasn't even being
called.&lt;/p&gt;
&lt;p&gt;After a lot of reading, both online documentation and other drivers' code, I
noticed that my driver was registering in the &lt;em&gt;SPMI&lt;/em&gt; bus, which made sense to me
since it needs to communicate over that bus, but given that the flash LED node
in the &lt;em&gt;devicetree&lt;/em&gt; was being registered in the &lt;em&gt;platform&lt;/em&gt; bus, my driver also
needed to, otherwise they would never bind. So that was something I needed to
change.&lt;/p&gt;
&lt;p&gt;But making my driver register on the &lt;em&gt;platform&lt;/em&gt; bus, made me no longer have the
&lt;code class="docutils literal"&gt;spmi_device&lt;/code&gt; pointer that I needed to use the SPMI functions to read and
write on the registers. Again, looking around on other drivers, like
&lt;code class="docutils literal"&gt;qcom-spmi-iadc&lt;/code&gt;, I discovered there was this cool &lt;em&gt;regmap&lt;/em&gt; thing I could use
instead to read and write on the registers over SPMI but without being specific
to SPMI. I did the reasonable thing and just tried it out!&lt;/p&gt;
&lt;p&gt;With &lt;a class="reference external" href="https://codeberg.org/nfraprado/linux/commit/2fc718b93bf69284e1f174397b1eb5255fd44359"&gt;these changes commited&lt;/a&gt;, the driver was now binding to the device, but
the &lt;em&gt;probe&lt;/em&gt; function was failing with the following messages:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="m"&gt;[   14.547394] &lt;/span&gt;&lt;span class="k"&gt;spmi spmi-0:&lt;/span&gt;&lt;span class="gr"&gt; pmic_arb_wait_for_done: transaction failed (0x3)&lt;/span&gt;
&lt;span class="m"&gt;[   14.547405] &lt;/span&gt;&lt;span class="k"&gt;qcom,leds-qpnp fc4cf000.spmi:&lt;/span&gt;pm8941@1:qcom,leds@d300: Unable to read from addr=5, rc(-5)
&lt;span class="m"&gt;[   14.547503] &lt;/span&gt;&lt;span class="k"&gt;qcom,leds-qpnp fc4cf000.spmi:&lt;/span&gt;&lt;span class="gr"&gt;pm8941@1:qcom,leds@d300: Regulator get failed(-517)&lt;/span&gt;
&lt;span class="m"&gt;[   14.547512] &lt;/span&gt;&lt;span class="k"&gt;qcom,leds-qpnp fc4cf000.spmi:&lt;/span&gt;pm8941@1:qcom,leds@d300: Unable to read flash config data
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Given that the regulator was the one failing, and that I had just copied over
the regulator nodes from the downstream &lt;em&gt;devicetree&lt;/em&gt;, the problem was clearly
there. I needed to find out what regulators were needed for the LED to work, and
add them upstream if they weren't already there.&lt;/p&gt;
&lt;p&gt;At this point I emailed Brian Masney asking for some light, and the advice he
gave me was to compile and test the downstream kernel. Having something that
works to use as a reference, even if it is very outdated, is invaluable.&lt;/p&gt;
&lt;p&gt;Using an older toolchain to compile, as &lt;a class="reference external" href="https://github.com/masneyb/nexus-5-upstream/blob/master/build-kernel"&gt;instructed in the build script&lt;/a&gt;, and
after &lt;a class="reference external" href="https://github.com/masneyb/nexus-5-upstream/pull/6"&gt;a minor problem&lt;/a&gt;, I got the downstream kernel compiling, flashed it, and
verified that the LED and the downstream driver indeed worked. I should really
have done this from the beginning... Imagine if the flash LED itself was
actually faulty!&lt;/p&gt;
&lt;p&gt;Then I started exploring the &lt;em&gt;sysfs&lt;/em&gt; on this system to see how it worked. I
found the regulator that was being used by the LED, whose &lt;code class="docutils literal"&gt;status&lt;/code&gt; went to
&lt;code class="docutils literal"&gt;enabled&lt;/code&gt; whenever I turned the LED on with &lt;code class="docutils literal"&gt;echo 1 &amp;gt;
/sys/class/leds/led\:flash_torch/brightness&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;With the downstream regulator node and &lt;em&gt;devicetree&lt;/em&gt;, and the upstream driver for
the regulator (&lt;code class="docutils literal"&gt;qcom_spmi-regulator&lt;/code&gt;) and its &lt;em&gt;dtbinding&lt;/em&gt; in hand, I started
doing some detective work.&lt;/p&gt;
&lt;p&gt;After some investigation I finally discovered that, given that the regulator
address was &lt;code class="docutils literal"&gt;0xa000&lt;/code&gt;, the regulator named &lt;code class="docutils literal"&gt;8941_boost&lt;/code&gt; downstream &lt;a class="reference external" href="https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=c333dfe8dba7d3e47e97e1cee3c38123e19ae73c"&gt;is known
as&lt;/a&gt; &lt;code class="docutils literal"&gt;s4&lt;/code&gt; in the upstream kernel, or also by its nickname &lt;code class="docutils literal"&gt;pm8941_5v&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;I still had to discover the other regulator's real identity, but having gained
some confidence from that fine detective work, and with some tips from the
&lt;em&gt;devicetree&lt;/em&gt;, I bet all my money that &lt;code class="docutils literal"&gt;pm8941_chg_boost&lt;/code&gt;'s real identity was
&lt;code class="docutils literal"&gt;5vs1&lt;/code&gt;, also called &lt;code class="docutils literal"&gt;pm8941_5vs1&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;&lt;a class="reference external" href="https://codeberg.org/nfraprado/linux/commit/e3ca691d5b726e64cab869d2dab57b835c14a5b9"&gt;Commit&lt;/a&gt;, flash, test, aaand... nope. It still didn't work, although I had
clearly made some progress. Now the &lt;em&gt;probe&lt;/em&gt; function was successfully being
executed, although the read and write operations on the SPMI registers were
still failing:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="m"&gt;[   13.346704] &lt;/span&gt;&lt;span class="k"&gt;leds_qpnp:&lt;/span&gt;qpnp_leds_probe: Probe called!
&lt;span class="m"&gt;[   13.346746] &lt;/span&gt;&lt;span class="k"&gt;spmi spmi-0:&lt;/span&gt;&lt;span class="gr"&gt; pmic_arb_wait_for_done: transaction failed (0x3)&lt;/span&gt;
&lt;span class="m"&gt;[   13.346760] &lt;/span&gt;&lt;span class="k"&gt;qcom,leds-qpnp fc4cf000.spmi:&lt;/span&gt;pm8941@1:qcom,leds@d300: Unable to read from addr=5, rc(-5)
&lt;span class="m"&gt;[   13.347250] &lt;/span&gt;&lt;span class="k"&gt;leds_qpnp:&lt;/span&gt;qpnp_dump_regs: ===== led:flash_0 LED register dump start =====
&lt;span class="m"&gt;[   13.347285] &lt;/span&gt;&lt;span class="k"&gt;leds_qpnp:&lt;/span&gt;qpnp_dump_regs: led:flash_0: 0x40 = 0x0
&lt;span class="m"&gt;[   13.347319] &lt;/span&gt;&lt;span class="k"&gt;leds_qpnp:&lt;/span&gt;qpnp_dump_regs: led:flash_0: 0x41 = 0x0
&lt;span class="m"&gt;[   13.347353] &lt;/span&gt;&lt;span class="k"&gt;leds_qpnp:&lt;/span&gt;qpnp_dump_regs: led:flash_0: 0x42 = 0x0
&lt;span class="m"&gt;[   13.347385] &lt;/span&gt;&lt;span class="k"&gt;leds_qpnp:&lt;/span&gt;qpnp_dump_regs: led:flash_0: 0x43 = 0x0
&lt;span class="m"&gt;[   13.347419] &lt;/span&gt;&lt;span class="k"&gt;leds_qpnp:&lt;/span&gt;qpnp_dump_regs: led:flash_0: 0x44 = 0x0
&lt;span class="m"&gt;[   13.347445] &lt;/span&gt;&lt;span class="k"&gt;leds_qpnp:&lt;/span&gt;qpnp_dump_regs: led:flash_0: 0x48 = 0x0
&lt;span class="m"&gt;[   13.347470] &lt;/span&gt;&lt;span class="k"&gt;leds_qpnp:&lt;/span&gt;qpnp_dump_regs: led:flash_0: 0x49 = 0x0
&lt;span class="m"&gt;[   13.347496] &lt;/span&gt;&lt;span class="k"&gt;leds_qpnp:&lt;/span&gt;qpnp_dump_regs: led:flash_0: 0x4b = 0x0
&lt;span class="m"&gt;[   13.347530] &lt;/span&gt;&lt;span class="k"&gt;leds_qpnp:&lt;/span&gt;qpnp_dump_regs: led:flash_0: 0x4c = 0x0
&lt;span class="m"&gt;[   13.347563] &lt;/span&gt;&lt;span class="k"&gt;leds_qpnp:&lt;/span&gt;qpnp_dump_regs: led:flash_0: 0x4f = 0x0
&lt;span class="m"&gt;[   13.347589] &lt;/span&gt;&lt;span class="k"&gt;leds_qpnp:&lt;/span&gt;qpnp_dump_regs: led:flash_0: 0x46 = 0x0
&lt;span class="m"&gt;[   13.347614] &lt;/span&gt;&lt;span class="k"&gt;leds_qpnp:&lt;/span&gt;qpnp_dump_regs: led:flash_0: 0x47 = 0x0
&lt;span class="m"&gt;[   13.347622] &lt;/span&gt;&lt;span class="k"&gt;leds_qpnp:&lt;/span&gt;qpnp_dump_regs: ===== led:flash_0 LED register dump end =====
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Since I was confident that the &lt;em&gt;devicetree&lt;/em&gt; was now right, I went back to the
driver code. I sprinkled some &lt;code class="docutils literal"&gt;pr_debug()&lt;/code&gt; over the &lt;em&gt;probe&lt;/em&gt; function and
noticed that the &lt;code class="docutils literal"&gt;reg&lt;/code&gt; value, which is read from the &lt;em&gt;devicetree&lt;/em&gt; and used as
the base address for all read and write operations, was &lt;code class="docutils literal"&gt;0&lt;/code&gt;, even though it
should be &lt;code class="docutils literal"&gt;0xd300&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Oh. Really?? Well, this wouldn't be a complete adventure without me introducing
a dumb bug myself, right? 😝&lt;/p&gt;
&lt;p&gt;After &lt;a class="reference external" href="https://codeberg.org/nfraprado/linux/commit/bd120825275df7157078992e6b9f29e8216a53b0"&gt;fixing the bug&lt;/a&gt;, I recompiled, reflashed, rebooted, retested aaand...
YESS!!!&lt;/p&gt;
&lt;img alt="Nexus 5's flashlight being turned on and off from the command line" src="/images/qpnp-leds/flash_led.gif" /&gt;
&lt;p&gt;Isn't it sooo beautiful? 😍&lt;/p&gt;
&lt;p&gt;But let's not get too amazed by that gorgeous light. Now that I finally had a
driver that actually worked, I rebased everything on top of mainline (at this
point the kernel had already moved from 5.7 to 5.9) to make sure it still worked
and sent an &lt;a class="reference external" href="https://lore.kernel.org/linux-arm-msm/20201106165737.1029106-1-nfraprado&amp;#64;protonmail.com/T/"&gt;RFC patch&lt;/a&gt;!&lt;/p&gt;
&lt;p&gt;And that's the tale of &amp;quot;How I ported a driver upstream&amp;quot;! Now, that's the end of
this post, but not of the adventure. There are lots of things that I need to do
to get that driver patch series actually included in mainline (some of which I
will still find out from the feedback on my patches).&lt;/p&gt;
&lt;p&gt;Since my goal here was always to just to get it working, changing as minimum as
possible, and only after that clean it up and make it decent, this is what I'll
need to start doing now 🙂.&lt;/p&gt;
&lt;p&gt;Just one last thing. This post might have made it seem that the problem
resolutions were streamlined, but it wasn't like that at all. There were
multiple points were I had no idea what to do, sat on it for multiple weeks, and
even thought about giving up and working on another project.&lt;/p&gt;
&lt;p&gt;But I'm glad I always kept trying and asking for help, because as much as it was
frustrating at times, it was also so much fun and I learned so much. I can't
even express my happiness the moment that light finally turned on after 4 months
(on and off) of work.&lt;/p&gt;
&lt;p&gt;And that's it! I hope to be back in a few months with a new post telling my
adventure on getting an RFC patch for a new driver into an actual upstream
driver 🙂. See you then!&lt;/p&gt;
</content><category term="2020"/><category term="nexus5"/><category term="kernel"/></entry><entry><title>Bulk file editing with ranger</title><link href="https://nfraprado.net/post/bulk-file-editing-with-ranger.html" rel="alternate"/><published>2020-10-20T00:00:00-03:00</published><updated>2020-10-20T00:00:00-03:00</updated><author><name>Nícolas F. R. A. Prado</name></author><id>tag:nfraprado.net,2020-10-20:/post/bulk-file-editing-with-ranger.html</id><summary type="html">&lt;p&gt;My file manager of choice is &lt;a class="reference external" href="https://ranger.github.io/"&gt;ranger&lt;/a&gt;. It's terminal-based, provides keybind
mapping for everything making me more efficient in navigating my files, and it's
incredibly extensible by enabling the creation of custom commands in &lt;strong&gt;python&lt;/strong&gt;.
If that wasn't enough, it also has a ton of other great features (extensible
file …&lt;/p&gt;</summary><content type="html">&lt;p&gt;My file manager of choice is &lt;a class="reference external" href="https://ranger.github.io/"&gt;ranger&lt;/a&gt;. It's terminal-based, provides keybind
mapping for everything making me more efficient in navigating my files, and it's
incredibly extensible by enabling the creation of custom commands in &lt;strong&gt;python&lt;/strong&gt;.
If that wasn't enough, it also has a ton of other great features (extensible
file preview, tabs, tags, ...). Go check out their &lt;a class="reference external" href="https://github.com/ranger/ranger"&gt;github page&lt;/a&gt;, seriously.&lt;/p&gt;
&lt;p&gt;One very useful feature of ranger is the &lt;code class="docutils literal"&gt;bulkrename&lt;/code&gt; command. It allows you
to open an editor with the filenames of all selected files and edit them. After
saving, a shell script is generated to make the necessary renaming (and you're
given the chance to review it), and just like magic &lt;em&gt;*poof*&lt;/em&gt; you just renamed
a bunch of files simultaneously from the convenience of your favorite text
editor.&lt;/p&gt;
&lt;p&gt;Here's an example:&lt;/p&gt;
&lt;img alt="Ranger's bulkrename command being used to rename three files in one go" src="/images/ranger-bulk/bulkrename.gif" /&gt;
&lt;p&gt;&lt;a class="reference external" href="https://asciinema.org/a/366010"&gt;At asciinema.org&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Pretty convenient. But when you think about it, it is rather limited. Why only
allow file renaming? This same workflow could be used to edit any file
attribute. One just needs to provide a way to obtain the attribute for each
selected file and to provide the shell script command that changes the attribute
to be what the user changed it to.&lt;/p&gt;
&lt;div class="section" id="the-bulk-command"&gt;
&lt;h2&gt;The bulk command&lt;/h2&gt;
&lt;p&gt;I recently wanted to modify the &lt;em&gt;Artist&lt;/em&gt; ID3 tag of multiple mp3 files at once
which motivated me to write the generic version of ranger's &lt;code class="docutils literal"&gt;bulkrename&lt;/code&gt;
command: &lt;code class="docutils literal"&gt;bulk&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;For that I basically copied the code for &lt;code class="docutils literal"&gt;bulkrename&lt;/code&gt; and generalized the file
attribute retrieval and the attribute change command generation, by calling the
&lt;code class="docutils literal"&gt;get_attribute()&lt;/code&gt; and &lt;code class="docutils literal"&gt;get_change_attribute_cmd()&lt;/code&gt;, respectively, from a
bulk subcommand stored in the &lt;code class="docutils literal"&gt;bulk&lt;/code&gt; dictionary.&lt;/p&gt;
&lt;p&gt;The &lt;code class="docutils literal"&gt;bulk&lt;/code&gt; command class is as follows:&lt;/p&gt;
&lt;pre class="code python literal-block"&gt;
&lt;span class="k"&gt;class&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nc"&gt;bulk&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Command&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="sd"&gt;&amp;quot;&amp;quot;&amp;quot;:bulk &amp;lt;attribute&amp;gt;

    This command opens a list with the attribute &amp;lt;attribute&amp;gt; for each of the
    selected files in an external editor.
    After you edit and save the file, it will generate a shell script
    which changes the attributes in bulk according to the changes you did in the
    file.

    This shell script is opened in an editor for you to review.
    After you close it, it will be executed.
    &amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;&lt;span class="w"&gt;


&lt;/span&gt;    &lt;span class="n"&gt;bulk&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;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;execute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;  &lt;span class="c1"&gt;# pylint: disable=too-many-locals,too-many-statements&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;        &lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;sys&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;        &lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;tempfile&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;        &lt;span class="kn"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;ranger.container.file&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;File&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;        &lt;span class="kn"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;ranger.ext.shell_escape&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;shell_escape&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;esc&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;        &lt;span class="n"&gt;py3&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;version_info&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;        &lt;span class="c1"&gt;# get bulk command argument&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;        &lt;span class="n"&gt;bkname&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;rest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;        &lt;span class="c1"&gt;# Create and edit the file list&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;        &lt;span class="n"&gt;files&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;fm&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;thistab&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get_selection&lt;/span&gt;&lt;span class="p"&gt;()]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;        &lt;span class="n"&gt;attributes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;bulk&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;bkname&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get_attribute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;files&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;        &lt;span class="n"&gt;listfile&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;tempfile&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NamedTemporaryFile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;delete&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;False&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;        &lt;span class="n"&gt;listpath&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;listfile&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;py3&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;            &lt;span class="n"&gt;listfile&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;attributes&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;encode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;utf-8&amp;quot;&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="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;            &lt;span class="n"&gt;listfile&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;attributes&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;        &lt;span class="n"&gt;listfile&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;close&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;fm&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;execute_file&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="n"&gt;File&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;listpath&lt;/span&gt;&lt;span class="p"&gt;)],&lt;/span&gt; &lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'editor'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;        &lt;span class="n"&gt;listfile&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;listpath&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'r'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;        &lt;span class="n"&gt;new_attributes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;listfile&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;read&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;        &lt;span class="n"&gt;listfile&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;close&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;        &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;unlink&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;listpath&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="nb"&gt;all&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nb"&gt;zip&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;attributes&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;new_attributes&lt;/span&gt;&lt;span class="p"&gt;)):&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;            &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;fm&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;notify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Nothing to be done!&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;            &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;        &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;new_attributes&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;        &lt;span class="c1"&gt;# Generate script&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;        &lt;span class="n"&gt;cmdfile&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;tempfile&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NamedTemporaryFile&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;        &lt;span class="n"&gt;script_lines&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="n"&gt;script_lines&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;# This file will be executed when you close the editor.&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;        &lt;span class="n"&gt;script_lines&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;# Please double-check everything, clear the file to abort.&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;        &lt;span class="n"&gt;script_lines&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;extend&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="si"&gt;%s&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;bulk&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;bkname&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get_change_attribute_cmd&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;file&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;old&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;new&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;                            &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;old&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;new&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;file&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;                            &lt;span class="nb"&gt;zip&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;attributes&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;new_attributes&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;files&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;old&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="n"&gt;new&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;        &lt;span class="n"&gt;script_content&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;&amp;quot;&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;script_lines&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="n"&gt;py3&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;            &lt;span class="n"&gt;cmdfile&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;script_content&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;encode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;utf-8&amp;quot;&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="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;            &lt;span class="n"&gt;cmdfile&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;script_content&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;        &lt;span class="n"&gt;cmdfile&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;flush&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;        &lt;span class="c1"&gt;# Open the script and let the user review it&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;fm&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;execute_file&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="n"&gt;File&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cmdfile&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;)],&lt;/span&gt; &lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'editor'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;        &lt;span class="n"&gt;cmdfile&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;seek&lt;/span&gt;&lt;span class="p"&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;# Do the attribute changing&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;fm&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;run&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="s1"&gt;'/bin/sh'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;cmdfile&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;flags&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'w'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;        &lt;span class="n"&gt;cmdfile&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;close&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/pre&gt;
&lt;p&gt;Then, inside this class, I added a class for each of the bulk subcommands that I
wanted to add: &lt;code class="docutils literal"&gt;id3art&lt;/code&gt;, &lt;code class="docutils literal"&gt;id3tit&lt;/code&gt; and &lt;code class="docutils literal"&gt;id3alb&lt;/code&gt;, which change the ID3 tag
for the &lt;em&gt;Artist&lt;/em&gt;, &lt;em&gt;Title&lt;/em&gt; and &lt;em&gt;Album&lt;/em&gt;, respectively.&lt;/p&gt;
&lt;p&gt;For each one of those, I implemented the &lt;code class="docutils literal"&gt;get_attribute()&lt;/code&gt; and
&lt;code class="docutils literal"&gt;get_change_attribute_cmd()&lt;/code&gt; methods. &lt;code class="docutils literal"&gt;get_attribute()&lt;/code&gt; receives a ranger's
file object and should return a string with the attribute, in the case of
&lt;code class="docutils literal"&gt;id3art&lt;/code&gt;, the ID3 &lt;em&gt;Artist&lt;/em&gt; tag, which I used the &lt;code class="docutils literal"&gt;eyed3&lt;/code&gt; python module to
get.  &lt;code class="docutils literal"&gt;get_change_attribute_cmd()&lt;/code&gt; receives a ranger's file object, the old
attribute (the one returned by &lt;code class="docutils literal"&gt;get_attribute()&lt;/code&gt;) and the new one (the value
edited by the user), and should return a string containing the shell script
command to apply the change made by the user (in the case of &lt;code class="docutils literal"&gt;id3art&lt;/code&gt;, &lt;code class="docutils literal"&gt;eyeD3
-a NewArtist fileName.mp3&lt;/code&gt;).&lt;/p&gt;
&lt;p&gt;Finally, I also added an entry for each of those subcommands to the &lt;code class="docutils literal"&gt;bulk&lt;/code&gt;
dictionary, which maps the subcommand name to its object.&lt;/p&gt;
&lt;p&gt;Translating all of this to code, this is what I added inside the &lt;code class="docutils literal"&gt;bulk&lt;/code&gt; class:&lt;/p&gt;
&lt;pre class="code python literal-block"&gt;
&lt;span class="k"&gt;class&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nc"&gt;id3art&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;object&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;    &lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;get_attribute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;file&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;        &lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;eyed3&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;        &lt;span class="n"&gt;artist&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;eyed3&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;load&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;file&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;relative_path&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;tag&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;artist&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;artist&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;artist&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="s1"&gt;''&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;    &lt;span class="w"&gt;
&lt;/span&gt;    &lt;span class="w"&gt;
&lt;/span&gt;    &lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;get_change_attribute_cmd&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;file&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;old&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;new&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;        &lt;span class="kn"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;ranger.ext.shell_escape&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;shell_escape&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;esc&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;eyeD3 -a &lt;/span&gt;&lt;span class="si"&gt;%s&lt;/span&gt;&lt;span class="s2"&gt; &lt;/span&gt;&lt;span class="si"&gt;%s&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;esc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;new&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;esc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;file&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="k"&gt;class&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nc"&gt;id3tit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;object&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;    &lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;get_attribute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;file&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;        &lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;eyed3&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;        &lt;span class="n"&gt;title&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;eyed3&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;load&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;file&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;relative_path&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;tag&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;title&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="s1"&gt;''&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;    &lt;span class="w"&gt;
&lt;/span&gt;    &lt;span class="w"&gt;
&lt;/span&gt;    &lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;get_change_attribute_cmd&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;file&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;old&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;new&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;        &lt;span class="kn"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;ranger.ext.shell_escape&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;shell_escape&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;esc&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;eyeD3 -t &lt;/span&gt;&lt;span class="si"&gt;%s&lt;/span&gt;&lt;span class="s2"&gt; &lt;/span&gt;&lt;span class="si"&gt;%s&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;esc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;new&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;esc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;file&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="k"&gt;class&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nc"&gt;id3alb&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;object&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;    &lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;get_attribute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;file&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;        &lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;eyed3&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;        &lt;span class="n"&gt;album&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;eyed3&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;load&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;file&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;relative_path&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;tag&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;album&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;album&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;album&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="s1"&gt;''&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;    &lt;span class="w"&gt;
&lt;/span&gt;    &lt;span class="w"&gt;
&lt;/span&gt;    &lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;get_change_attribute_cmd&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;file&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;old&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;new&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;        &lt;span class="kn"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;ranger.ext.shell_escape&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;shell_escape&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;esc&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;eyeD3 -A &lt;/span&gt;&lt;span class="si"&gt;%s&lt;/span&gt;&lt;span class="s2"&gt; &lt;/span&gt;&lt;span class="si"&gt;%s&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;esc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;new&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;esc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;file&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="n"&gt;bulk&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s1"&gt;'id3art'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;id3art&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;        &lt;span class="s1"&gt;'id3tit'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;id3tit&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;        &lt;span class="s1"&gt;'id3alb'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;id3alb&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;/pre&gt;
&lt;p&gt;Here it is in action:&lt;/p&gt;
&lt;img alt="The id3art bulk subcommand being used to change the ID3 artist tag of three mp3 files in one go" src="/images/ranger-bulk/bulk.gif" /&gt;
&lt;p&gt;&lt;a class="reference external" href="https://asciinema.org/a/366013"&gt;At asciinema.org&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Since I thought this generic framework for the bulk command could be useful to
everyone using ranger, with each user implementing their own bulk subcommand, I
opened a &lt;a class="reference external" href="https://github.com/ranger/ranger/pull/2109"&gt;pull request&lt;/a&gt;. It seems the idea was well received, and they even
intend to make &lt;code class="docutils literal"&gt;bulkrename&lt;/code&gt; simply an alias to a bulk subcommand, so maybe in
the near future you will be able to make your own bulk subcommands using the
&lt;strong&gt;built-in&lt;/strong&gt; &lt;code class="docutils literal"&gt;bulk&lt;/code&gt; command 🙂.&lt;/p&gt;
&lt;/div&gt;
</content><category term="2020"/><category term="ranger"/><category term="python"/></entry><entry><title>Playlist generation with MPD</title><link href="https://nfraprado.net/post/playlist-generation-with-mpd.html" rel="alternate"/><published>2020-09-20T00:00:00-03:00</published><updated>2020-09-20T00:00:00-03:00</updated><author><name>Nícolas F. R. A. Prado</name></author><id>tag:nfraprado.net,2020-09-20:/post/playlist-generation-with-mpd.html</id><summary type="html">&lt;p&gt;Music is life. I really love listening to music, although not the same kind of
music all the time. Most of the time though, anything goes: I like to listen to
any of the songs I have at random. But when I'm doing something that needs
concentrating (like writing this …&lt;/p&gt;</summary><content type="html">&lt;p&gt;Music is life. I really love listening to music, although not the same kind of
music all the time. Most of the time though, anything goes: I like to listen to
any of the songs I have at random. But when I'm doing something that needs
concentrating (like writing this text) I can only listen to what I call
&amp;quot;background&amp;quot; music, or music that doesn't have vocals. So having groupings of
songs is very useful for this kind of situation.&lt;/p&gt;
&lt;p&gt;One way of grouping songs is through playlists. But the criteria to determine
which songs go into a playlist is very much subjective since, well, it's made up
by me, a human being. This is what motivated me to create some way to generate
playlists based on varied criteria: folder names, artists, albums, etc.&lt;/p&gt;
&lt;p&gt;Recently, I read the incredible book &lt;a class="reference external" href="https://www.amazon.com/Fluent-Python-Concise-Effective-Programming/dp/1491946008"&gt;Fluent Python&lt;/a&gt; as part of furthering my
skills in python, which has easily become my favorite programming language for
some time now. I learned a lot reading that book, but hadn't actually practiced
any of it yet. One of the most interesting things that I learned was about the
&lt;a class="reference external" href="https://docs.python.org/3/reference/datamodel.html#special-method-names"&gt;special methods&lt;/a&gt; that make python objects very flexible. I then realized that
using special methods I could make playlist objects that were very flexible
while also practicing this magic concept. Win-win.&lt;/p&gt;
&lt;p&gt;The program that I use to track my music library and play the music is &lt;a class="reference external" href="https://www.musicpd.org/"&gt;MPD&lt;/a&gt;.
It supports querying data about songs and also playing playlists, so it was
straight-forward to make my playlist generation script using MPD.&lt;/p&gt;
&lt;div class="section" id="the-mpdplaylist-class"&gt;
&lt;h2&gt;The MPDPlaylist class&lt;/h2&gt;
&lt;p&gt;This is the idea: I want to be able to create a playlist by specifying what
should be in it, and also what shouldn't, and be able to combine criteria from
other playlists using AND and OR operations. This will probably become clearer
later with the examples.&lt;/p&gt;
&lt;p&gt;The code implementing this class is in &lt;code class="docutils literal"&gt;mpd_playlist.py&lt;/code&gt;, which is the
following:&lt;/p&gt;
&lt;pre class="code python literal-block"&gt;
&lt;span class="kn"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;mpd&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;MPDClient&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;MPDClient&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;timeout&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;60&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;idletimeout&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;None&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;connect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;localhost&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;6600&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="k"&gt;class&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nc"&gt;MPDPlaylist&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;    &lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="fm"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;''&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nb"&gt;isinstance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="vm"&gt;__class__&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;            &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;songs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;songs&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;        &lt;span class="k"&gt;elif&lt;/span&gt; &lt;span class="nb"&gt;isinstance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;set&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;            &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;songs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;query&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="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;            &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;songs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;query_songs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;


&lt;/span&gt;    &lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="fm"&gt;__repr__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="vm"&gt;__class__&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;(&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;, &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;songs&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;)&amp;quot;&lt;/span&gt;&lt;span class="w"&gt;


&lt;/span&gt;    &lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="fm"&gt;__or__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;other&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;MPDPlaylist&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;songs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;union&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;other&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;songs&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="w"&gt;


&lt;/span&gt;    &lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="fm"&gt;__and__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;other&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;MPDPlaylist&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;songs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;intersection&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;other&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;songs&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="w"&gt;


&lt;/span&gt;    &lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="fm"&gt;__neg__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;MPDPlaylist&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;MPDPlaylist&lt;/span&gt;&lt;span class="p"&gt;({})&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;songs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;difference&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;songs&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="w"&gt;


&lt;/span&gt;    &lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;query_songs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;queries&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="n"&gt;queries&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;            &lt;span class="n"&gt;args&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;for&lt;/span&gt; &lt;span class="n"&gt;k&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;v&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;queries&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;items&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;                &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;k&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;                &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nb"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;song&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'file'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;song&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;search&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;args&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="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nb"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;song&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'file'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;song&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;listall&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;                       &lt;span class="n"&gt;song&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'file'&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="w"&gt;


&lt;/span&gt;    &lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;write_to_file&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;base&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="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&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="k"&gt;with&lt;/span&gt; &lt;span class="nb"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;base&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;.m3u&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'w'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;                &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;songs&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s1"&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="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;            &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Can't write playlist with no name!&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;
&lt;p&gt;That &lt;code class="docutils literal"&gt;MPDClient()&lt;/code&gt; thing in the beginning is needed to connect to the MPD
database to later retrieve all music information. It is provided by the
&lt;a class="reference external" href="https://github.com/Mic92/python-mpd2"&gt;python-mpd2&lt;/a&gt; package.&lt;/p&gt;
&lt;p&gt;The &lt;code class="docutils literal"&gt;__init__()&lt;/code&gt; method, which is called when creating a new playlist object,
can accept another playlist as its query parameter, in which case the new
playlist just copies the songs from the playlist passed. It can also receive a
set of songs (which I never actually used, but it made sense to support). And
finally, for the most common use, it can receive a dictionary containing the
query that will be used to filter the music from MPD. For all cases, an optional
name can also be passed for the playlist (needed for the playlist to be saved).&lt;/p&gt;
&lt;p&gt;So, for example, &lt;code class="docutils literal"&gt;paramore_playlist = MPDPlaylist({'artist': 'Paramore'})&lt;/code&gt;
would create a playlist only with songs from Paramore, and then
&lt;code class="docutils literal"&gt;paramore_playlist2 = MPDPlaylist(paramore_playlist)&lt;/code&gt; could be used to create
a copy of that playlist.  This second case doesn't sound as useful but is the
base of parsing expressions, as we'll see.&lt;/p&gt;
&lt;p&gt;&lt;code class="docutils literal"&gt;__repr__()&lt;/code&gt; is just there for debugging. It specifies how the object is
printed, showing the songs the playlist contains.&lt;/p&gt;
&lt;p&gt;&lt;code class="docutils literal"&gt;__or__()&lt;/code&gt; and &lt;code class="docutils literal"&gt;__and__()&lt;/code&gt; are called when two playlists are OR'ed (using
&lt;code class="docutils literal"&gt;|&lt;/code&gt;) and AND'ed (using &lt;code class="docutils literal"&gt;&amp;amp;&lt;/code&gt;) together, yielding a playlist that contains the
union (songs from both playlists) and the intersection (only songs that were in
both playlists) respectively. &lt;code class="docutils literal"&gt;__neg__()&lt;/code&gt; is for negating the playlist (using
&lt;code class="docutils literal"&gt;-&lt;/code&gt;), making so that the query specifies what the playlist should &lt;strong&gt;not&lt;/strong&gt;
contain, so it will contain everything but that.&lt;/p&gt;
&lt;p&gt;&lt;code class="docutils literal"&gt;query_songs()&lt;/code&gt; uses the query to get all songs that match it from MPD and
saves them in the object.&lt;/p&gt;
&lt;p&gt;&lt;code class="docutils literal"&gt;write_to_file()&lt;/code&gt; saves the playlist in a &lt;code class="docutils literal"&gt;.m3u&lt;/code&gt; file so that it can be
later read and played by MPD. The name of the file is the name given in
&lt;code class="docutils literal"&gt;__init__&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Now, to some examples.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="my-playlists"&gt;
&lt;h2&gt;My playlists&lt;/h2&gt;
&lt;p&gt;In another file, &lt;code class="docutils literal"&gt;playlists.py&lt;/code&gt;, I have all of my playlists defined using the
MPDPlaylist class:&lt;/p&gt;
&lt;pre class="code python literal-block"&gt;
&lt;span class="kn"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;mpd_playlist&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;MPDPlaylist&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;PL&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="n"&gt;saved&lt;/span&gt;  &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;PL&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="s1"&gt;'base'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;genres&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;0_Saved&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;buffer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;PL&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="s1"&gt;'base'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;buffer&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;1_Buffer&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;fvt&lt;/span&gt;    &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;PL&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="s1"&gt;'file'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;/%&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;1_FvtAlbums&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="n"&gt;br&lt;/span&gt;           &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;PL&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="s1"&gt;'base'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;genres/br&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;           &lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;2_Br&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;classical&lt;/span&gt;    &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;PL&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="s1"&gt;'base'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;genres/classical&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;    &lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;2_Classical&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;electronic&lt;/span&gt;   &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;PL&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="s1"&gt;'base'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;genres/electronic&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;   &lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;2_Electronic&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;etc&lt;/span&gt;          &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;PL&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="s1"&gt;'base'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;genres/etc&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;          &lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;2_ETC&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;instrumental&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;PL&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="s1"&gt;'base'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;genres/instrumental&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;2_Instrumental&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;jazz&lt;/span&gt;         &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;PL&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="s1"&gt;'base'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;genres/jazz&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;         &lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;2_Jazz&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;pop&lt;/span&gt;          &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;PL&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="s1"&gt;'base'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;genres/pop&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;          &lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;2_Pop&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;post_rock&lt;/span&gt;    &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;PL&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="s1"&gt;'base'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;genres/post-rock&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;    &lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;2_Post-rock&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;rap&lt;/span&gt;          &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;PL&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="s1"&gt;'base'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;genres/rap&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;          &lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;2_Rap&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;rock&lt;/span&gt;         &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;PL&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="s1"&gt;'base'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;genres/rock&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;         &lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;2_Rock&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;soundtrack&lt;/span&gt;   &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;PL&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="s1"&gt;'base'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;genres/soundtrack&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;   &lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;2_Sountrack&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="n"&gt;common&lt;/span&gt;         &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;PL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rock&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;electronic&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;pop&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;post_rock&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;rap&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;1_Common&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;not_soundtrack&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;PL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;soundtrack&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;2_NotSoundtrack&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="n"&gt;tdg&lt;/span&gt;       &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;PL&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="s1"&gt;'artist'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;Three Days Grace&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;3_TDG&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;minecraft&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;PL&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="s1"&gt;'base'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;genres/soundtrack/games/%minecraft&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;3_Minecraft&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;lotr&lt;/span&gt;      &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;PL&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="s1"&gt;'base'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;genres/soundtrack/movies/lotr&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;3_LOTR&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="n"&gt;background&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;PL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;classical&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;                &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;jazz&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;                &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;instrumental&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;                &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;PL&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="s1"&gt;'artist'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;Balmorhea&amp;quot;&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="n"&gt;PL&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="s1"&gt;'artist'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;Tycho&amp;quot;&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="n"&gt;PL&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="s1"&gt;'artist'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;MASTER BOOT RECORD&amp;quot;&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="n"&gt;PL&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="s1"&gt;'artist'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;The Album Leaf&amp;quot;&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="n"&gt;PL&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="s1"&gt;'base'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;genres/soundtrack/animes/tatami_galaxy/&lt;/span&gt;&lt;span class="si"&gt;%o&lt;/span&gt;&lt;span class="s2"&gt;st&amp;quot;&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="n"&gt;PL&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="s1"&gt;'base'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;genres/soundtrack/games&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;                &lt;span class="o"&gt;&amp;amp;&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;PL&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="s1"&gt;'file'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;genres/soundtrack/games/Portal - Still Alive.mp3&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;                &lt;span class="o"&gt;&amp;amp;&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;PL&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="s1"&gt;'file'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;genres/soundtrack/games/portal_2/3-13_want_you_gone.mp3&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;                &lt;span class="o"&gt;&amp;amp;&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;PL&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="s1"&gt;'title'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;Here Comes Vi&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;}),&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;                &lt;span class="s2"&gt;&amp;quot;1_Background&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;
&lt;p&gt;The first playlists created are:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;code class="docutils literal"&gt;saved&lt;/code&gt;, containing all songs that are in the &lt;code class="docutils literal"&gt;genres&lt;/code&gt; folder, which is
essentially my music library;&lt;/li&gt;
&lt;li&gt;&lt;code class="docutils literal"&gt;buffer&lt;/code&gt;, with all songs that are inside the &lt;code class="docutils literal"&gt;buffer&lt;/code&gt; folder, which are
the ones I'm still listening to decide if I like or not;&lt;/li&gt;
&lt;li&gt;&lt;code class="docutils literal"&gt;fvt&lt;/code&gt; with all songs from folders beginning with a &lt;code class="docutils literal"&gt;%&lt;/code&gt;, which are my
favorite albums.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Then there are playlists for each of the genres. Some of those, namely &lt;code class="docutils literal"&gt;rock&lt;/code&gt;,
&lt;code class="docutils literal"&gt;electronic&lt;/code&gt;, &lt;code class="docutils literal"&gt;pop&lt;/code&gt;, &lt;code class="docutils literal"&gt;post_rock&lt;/code&gt; and &lt;code class="docutils literal"&gt;rap&lt;/code&gt; are then combined with the OR
(&lt;code class="docutils literal"&gt;|&lt;/code&gt;) operator to create the &lt;code class="docutils literal"&gt;common&lt;/code&gt; playlist. This means that this
playlist contains the music from all of those playlists combined.&lt;/p&gt;
&lt;p&gt;Next, the &lt;code class="docutils literal"&gt;not_soundtrack&lt;/code&gt; playlist is created by negating the &lt;code class="docutils literal"&gt;soundtrack&lt;/code&gt;
playlist, so the former only contains the music not present in the latter.&lt;/p&gt;
&lt;p&gt;The &lt;code class="docutils literal"&gt;tdg&lt;/code&gt; playlist only has music from the &lt;code class="docutils literal"&gt;Three Days Grace&lt;/code&gt; artist.&lt;/p&gt;
&lt;p&gt;The last playlist, &lt;code class="docutils literal"&gt;background&lt;/code&gt;, combines several previously defined playlists
like &lt;code class="docutils literal"&gt;classical&lt;/code&gt;, and also anonymous playlists like &lt;code class="docutils literal"&gt;PL({'artist':
&amp;quot;Balmorhea&amp;quot;})&lt;/code&gt; (which are just used to create this playlist, and won't be saved
as playlists themselves, therefore they aren't saved in a variable and also
don't need a name parameter), while also removing specific songs like
&lt;code class="docutils literal"&gt;PL({'file': &amp;quot;genres/soundtrack/games/Portal - Still Alive.mp3&amp;quot;})&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;This isn't the most succinct syntax but it also isn't bad and is very flexible:
It served all my needs for customizing my playlists.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="saving-the-playlists"&gt;
&lt;h2&gt;Saving the playlists&lt;/h2&gt;
&lt;p&gt;Now, you might be wondering how those playlists are written to the files if they
are only created and saved in variables. The answer is: python is cool 😃. This
is &lt;code class="docutils literal"&gt;gen_playlists.py&lt;/code&gt;, the cool script that does it:&lt;/p&gt;
&lt;pre class="code python literal-block"&gt;
&lt;span class="ch"&gt;#!/bin/python&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;os.path&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;expanduser&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;mpd_playlist&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;MPDPlaylist&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;playlists&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="n"&gt;PLD&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;expanduser&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;~/.config/mpd/playlists&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="c1"&gt;# All variables defined locally with type MPDPlaylist and with a name will be&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="c1"&gt;# contained here&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;playlists&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pl&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;pl&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nb"&gt;vars&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;playlists&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;values&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="nb"&gt;isinstance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pl&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;MPDPlaylist&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;pl&lt;/span&gt;&lt;span class="o"&gt;.&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="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;playlist&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;playlists&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;    &lt;span class="n"&gt;playlist&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;write_to_file&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;PLD&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;
&lt;p&gt;As that nice comment above it says, &lt;code class="docutils literal"&gt;playlists = (pl for pl in
vars(playlists).values() if isinstance(pl, MPDPlaylist) and pl.name)&lt;/code&gt; gets all
variables of type &lt;code class="docutils literal"&gt;MPDPlaylist&lt;/code&gt; from &lt;code class="docutils literal"&gt;playlists.py&lt;/code&gt;, as long as they have a
name. Then the for loop iterates over them and saves each playlist in a file
with its name.&lt;/p&gt;
&lt;p&gt;Finally, I added a line in &lt;code class="docutils literal"&gt;cron&lt;/code&gt; to run this script every Sunday, updating my
playlists.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="the-newest-playlist"&gt;
&lt;h2&gt;The 'newest' playlist&lt;/h2&gt;
&lt;p&gt;Since nothing is perfect, there's one playlist that I couldn't integrate in that
framework and was left as its own shell script 🤢: the &lt;code class="docutils literal"&gt;newest&lt;/code&gt; playlist. It
contains the 100 latest songs added to my music library.&lt;/p&gt;
&lt;p&gt;There are four different timestamps for files according to &lt;code class="docutils literal"&gt;stat&lt;/code&gt;:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;time of file birth&lt;/li&gt;
&lt;li&gt;time of last access&lt;/li&gt;
&lt;li&gt;time of last data modification&lt;/li&gt;
&lt;li&gt;time of last status change&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;To get the latest songs added, and not the ones most recently edited (sometimes
I edit song metadata, and don't want it to interfere with this playlist), I
needed to use time of birth, but it isn't supported by MPD, so that's why I'm
stuck with this &lt;code class="docutils literal"&gt;gen_playlist_newest.sh&lt;/code&gt; shell script:&lt;/p&gt;
&lt;pre class="code shell literal-block"&gt;
&lt;span class="nv"&gt;PLD&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;$HOME&lt;/span&gt;/.config/mpd/playlists&lt;span class="w"&gt;
&lt;/span&gt;find&lt;span class="w"&gt; &lt;/span&gt;~/ark/mus/genres&lt;span class="w"&gt; &lt;/span&gt;-type&lt;span class="w"&gt; &lt;/span&gt;f&lt;span class="w"&gt; &lt;/span&gt;-regextype&lt;span class="w"&gt; &lt;/span&gt;posix-extended&lt;span class="w"&gt; &lt;/span&gt;-regex&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;'.*\.(flac|mp3)'&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;-exec&lt;span class="w"&gt; &lt;/span&gt;stat&lt;span class="w"&gt; &lt;/span&gt;--format&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;'%W : %n'&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;{}&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&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;sort&lt;span class="w"&gt; &lt;/span&gt;-nr&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;head&lt;span class="w"&gt; &lt;/span&gt;-n&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;100&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;cut&lt;span class="w"&gt; &lt;/span&gt;-d:&lt;span class="w"&gt; &lt;/span&gt;-f2&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;sed&lt;span class="w"&gt; &lt;/span&gt;-e&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;'s|.*mus/\(.*\)|\1|'&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\
&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nv"&gt;$PLD&lt;/span&gt;/1_Newest.m3u
&lt;/pre&gt;
&lt;/div&gt;
</content><category term="2020"/><category term="mpd"/><category term="python"/></entry><entry><title>Automatic context detection for Taskwarrior</title><link href="https://nfraprado.net/post/automatic-context-detection-for-taskwarrior.html" rel="alternate"/><published>2020-08-24T00:00:00-03:00</published><updated>2020-08-24T00:00:00-03:00</updated><author><name>Nícolas F. R. A. Prado</name></author><id>tag:nfraprado.net,2020-08-24:/post/automatic-context-detection-for-taskwarrior.html</id><summary type="html">&lt;p&gt;One of the main ideas of &lt;a class="reference external" href="https://en.wikipedia.org/wiki/Getting_Things_Done"&gt;GTD&lt;/a&gt; is to have a context associated with each task, so
that it is very easy to see which tasks can be done in your current context. I
organize my tasks with &lt;a class="reference external" href="https://taskwarrior.org/"&gt;Taskwarrior&lt;/a&gt;, so to make it work with contexts, when
adding a new …&lt;/p&gt;</summary><content type="html">&lt;p&gt;One of the main ideas of &lt;a class="reference external" href="https://en.wikipedia.org/wiki/Getting_Things_Done"&gt;GTD&lt;/a&gt; is to have a context associated with each task, so
that it is very easy to see which tasks can be done in your current context. I
organize my tasks with &lt;a class="reference external" href="https://taskwarrior.org/"&gt;Taskwarrior&lt;/a&gt;, so to make it work with contexts, when
adding a new task I need to assign a context to it and also update the current
context whenever it changes. There are different types of context, but the
easiest to automate are the spatial ones, that is, which tasks I can do
&lt;strong&gt;where&lt;/strong&gt; I'm at right now.&lt;/p&gt;
&lt;p&gt;Assigning a task to a context is as simple as adding a tag to it. I have three
different places I'm normally at, so I either add a &lt;code class="docutils literal"&gt;&amp;#64;rep&lt;/code&gt;, &lt;code class="docutils literal"&gt;&amp;#64;sp&lt;/code&gt; or
&lt;code class="docutils literal"&gt;&amp;#64;uni&lt;/code&gt; tag depending on where the task can be done.&lt;/p&gt;
&lt;p&gt;To update the current context though I'd need to manually tell Taskwarrior where
I'm at right now every time I go to any other place. For example, I'd need to
type &lt;code class="docutils literal"&gt;task context uni&lt;/code&gt; every time I went to the university. This is not only
very boring but also error-prone: more than once it took me a couple seconds to
understand why some tasks were missing.&lt;/p&gt;
&lt;p&gt;As any other little problem in life, this can be solved with a little scripting.
So that's why I wrote a python script to automatically detect and set the
current context for Taskwarrior.&lt;/p&gt;
&lt;div class="section" id="python-script"&gt;
&lt;h2&gt;Python script&lt;/h2&gt;
&lt;p&gt;The idea is very simple: I'm almost always connected to the WiFi, since it
connects automatically, and each location has specific WiFi network names, so I
just need to get the current WiFi SSID and set the corresponding context.&lt;/p&gt;
&lt;p&gt;That is what the following python script does (aside from sending a notification
with &lt;code class="docutils literal"&gt;notify-send&lt;/code&gt;):&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;subprocess&lt;/span&gt;


&lt;span class="n"&gt;contexts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="s1"&gt;&amp;#39;rep&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;rep wifi 1&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;rep wifi 2&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;rep wifi 3&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="s1"&gt;&amp;#39;sp&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;  &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;sp wifi 1&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;sp wifi 2&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="s1"&gt;&amp;#39;uni&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;eduroam&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;get_context&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;wifi&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;contexts&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;wifi&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;contexts&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;None&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;get_current_context&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="n"&gt;wifi_cmd&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;subprocess&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;run&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;iwgetid&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;-r&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;capture_output&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;wifi&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;wifi_cmd&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;stdout&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;get_context&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;wifi&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;set_current_context&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="n"&gt;context&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;get_current_context&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;subprocess&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;run&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;notify-send&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;Taskwarrior context&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                       &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Setting context to &amp;lt;b&amp;gt;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;lt;/b&amp;gt;&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
        &lt;span class="n"&gt;subprocess&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;run&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;task&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;context&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
    &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;subprocess&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;run&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;notify-send&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;-u&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;critical&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;Taskwarrior context&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                       &lt;span class="s2"&gt;&amp;quot;Failure to detect context&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
        &lt;span class="n"&gt;subprocess&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;run&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;task&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;context&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;none&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;Note&lt;/strong&gt;: The WiFi names for the &lt;code class="docutils literal"&gt;rep&lt;/code&gt; and &lt;code class="docutils literal"&gt;sp&lt;/code&gt; contexts were redacted to
avoid exposure.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="systemd-service"&gt;
&lt;h2&gt;Systemd service&lt;/h2&gt;
&lt;p&gt;Since when I go from one place to the other I always suspend, hibernate or
shutdown my notebook, that script only needs to be run after resuming or turning
the computer on, which is exactly what the following systemd service is for:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;[Unit]&lt;/span&gt;
&lt;span class="na"&gt;Description&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;Set taskwarrior context&lt;/span&gt;
&lt;span class="na"&gt;Wants&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;network-online.target NetworkManager-wait-online.service&lt;/span&gt;
&lt;span class="na"&gt;After&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;network-online.target NetworkManager-wait-online.service hibernate.target suspend.target&lt;/span&gt;

&lt;span class="k"&gt;[Service]&lt;/span&gt;
&lt;span class="na"&gt;User&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;%I&lt;/span&gt;
&lt;span class="na"&gt;Type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;oneshot&lt;/span&gt;
&lt;span class="na"&gt;Environment&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;PATH=/usr/bin:/home/nfraprado/ark/code/.path/&lt;/span&gt;
&lt;span class="na"&gt;Environment&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;DISPLAY=:0&lt;/span&gt;
&lt;span class="na"&gt;Environment&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;XAUTHORITY=%h/.Xauthority&lt;/span&gt;
&lt;span class="na"&gt;Environment&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/1000/bus&lt;/span&gt;
&lt;span class="na"&gt;ExecStartPre&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;/usr/bin/sleep 30&lt;/span&gt;
&lt;span class="na"&gt;ExecStart&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;/home/nfraprado/ark/code/.path/taskwarrior-update_context&lt;/span&gt;
&lt;span class="na"&gt;ExecStartPost&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;

&lt;span class="k"&gt;[Install]&lt;/span&gt;
&lt;span class="na"&gt;WantedBy&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;multi-user.target&lt;/span&gt;
&lt;span class="na"&gt;WantedBy&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;hibernate.target&lt;/span&gt;
&lt;span class="na"&gt;WantedBy&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;suspend.target&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Details about this service file:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;The &lt;code class="docutils literal"&gt;network-online&lt;/code&gt; and &lt;code class="docutils literal"&gt;wait-online&lt;/code&gt; services supposedly make it wait
for NetworkManager to connect to a network before executing, but from my tests
that wasn't enough, so I ended up adding a 30 second delay as can be seen in
&lt;code class="docutils literal"&gt;ExecStartPre&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;The &lt;code class="docutils literal"&gt;hibernate&lt;/code&gt; and &lt;code class="docutils literal"&gt;suspend&lt;/code&gt; targets make it run after the computer
resumes or turns on.&lt;/li&gt;
&lt;li&gt;The &lt;code class="docutils literal"&gt;DISPLAY&lt;/code&gt;, &lt;code class="docutils literal"&gt;XAUTHORITY&lt;/code&gt; and &lt;code class="docutils literal"&gt;DBUS_SESSION_BUS_ADDRESS&lt;/code&gt; environment
variables allow the notification to appear.&lt;/li&gt;
&lt;li&gt;&lt;code class="docutils literal"&gt;taskwarrior-update_context&lt;/code&gt; basically calls &lt;code class="docutils literal"&gt;set_current_context()&lt;/code&gt; from
the python script.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;And that's it! With those two pieces, after I move between two locations and
open my notebook the Taskwarrior context is automatically updated, showing me
only the tasks relevant to the place I'm at.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="extra-previous-script"&gt;
&lt;h2&gt;Extra: Previous script&lt;/h2&gt;
&lt;p&gt;As a side note, before that python script, I made a bash script. Even though
running a command in bash is way cleaner than python's &lt;code class="docutils literal"&gt;subprocess.run()&lt;/code&gt;, I
really despise bash's syntax. I also find it awful to need to define a
&lt;code class="docutils literal"&gt;array_contains&lt;/code&gt; function (which I copied from some StackOverflow answer).
Anyway, here's the bash script if you're curious:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="ch"&gt;#!/bin/bash&lt;/span&gt;
&lt;span class="nv"&gt;wifi&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="k"&gt;$(&lt;/span&gt;iwgetid&lt;span class="w"&gt; &lt;/span&gt;-r&lt;span class="k"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;

&lt;span class="nb"&gt;declare&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;-a&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;rep&lt;/span&gt;&lt;span class="o"&gt;=(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;rep wifi 1&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;rep wifi 2&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;rep wifi 3&amp;quot;&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="nb"&gt;declare&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;-a&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;sp&lt;/span&gt;&lt;span class="o"&gt;=(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;sp wifi 1&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;sp wifi 2&amp;quot;&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="nb"&gt;declare&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;-a&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;uni&lt;/span&gt;&lt;span class="o"&gt;=(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;eduroam&amp;quot;&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;

array_contains&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="o"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nb"&gt;local&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;array&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="nv"&gt;$1&lt;/span&gt;&lt;span class="s2"&gt;[@]&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nb"&gt;local&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;seeking&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;$2&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nb"&gt;local&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;in&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;element&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="p"&gt;!array&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;do&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="o"&gt;[[&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$element&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="nv"&gt;$seeking&lt;/span&gt;&lt;span class="w"&gt; &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;then&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="k"&gt;in&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="k"&gt;break&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;fi&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;done&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$in&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;

array_contains&lt;span class="w"&gt; &lt;/span&gt;rep&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="nv"&gt;$wifi&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;context&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;rep
array_contains&lt;span class="w"&gt; &lt;/span&gt;sp&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="nv"&gt;$wifi&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;context&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;sp
array_contains&lt;span class="w"&gt; &lt;/span&gt;uni&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="nv"&gt;$wifi&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;context&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;uni

&lt;span class="k"&gt;if&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;-z&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="nv"&gt;$context&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="k"&gt;then&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;notify-send&lt;span class="w"&gt; &lt;/span&gt;-u&lt;span class="w"&gt; &lt;/span&gt;critical&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Taskwarrior context&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Failure to detect context&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;task&lt;span class="w"&gt; &lt;/span&gt;context&lt;span class="w"&gt; &lt;/span&gt;none&lt;span class="w"&gt; &lt;/span&gt;&amp;gt;/dev/null&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;&amp;gt;&lt;span class="p"&gt;&amp;amp;&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;
&lt;span class="k"&gt;else&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;notify-send&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Taskwarrior context&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Setting context to &amp;lt;b&amp;gt;&lt;/span&gt;&lt;span class="nv"&gt;$context&lt;/span&gt;&lt;span class="s2"&gt;&amp;lt;/b&amp;gt;&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;task&lt;span class="w"&gt; &lt;/span&gt;context&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$context&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&amp;gt;/dev/null&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;&amp;gt;&lt;span class="p"&gt;&amp;amp;&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;
&lt;span class="k"&gt;fi&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
</content><category term="2020"/><category term="taskwarrior"/><category term="gtd"/><category term="python"/></entry><entry><title>Setting up mbsync to work with XOAUTH2</title><link href="https://nfraprado.net/post/setting-up-mbsync-to-work-with-xoauth2.html" rel="alternate"/><published>2020-07-30T00:00:00-03:00</published><updated>2020-07-30T00:00:00-03:00</updated><author><name>Nícolas F. R. A. Prado</name></author><id>tag:nfraprado.net,2020-07-30:/post/setting-up-mbsync-to-work-with-xoauth2.html</id><summary type="html">&lt;p&gt;For a long time I used &lt;a class="reference external" href="https://www.offlineimap.org/"&gt;offlineimap&lt;/a&gt; to synchronize my emails between the email
providers and my computer. Having access to all my emails offline on my computer
is pretty handy. But after seeing the &lt;a class="reference external" href="https://people.kernel.org/mcgrof/replacing-offlineimap-with-mbsync"&gt;brutal efficiency advantage of mbsync
over offlineimap&lt;/a&gt;, and having had delay issues with offlineimap myself …&lt;/p&gt;</summary><content type="html">&lt;p&gt;For a long time I used &lt;a class="reference external" href="https://www.offlineimap.org/"&gt;offlineimap&lt;/a&gt; to synchronize my emails between the email
providers and my computer. Having access to all my emails offline on my computer
is pretty handy. But after seeing the &lt;a class="reference external" href="https://people.kernel.org/mcgrof/replacing-offlineimap-with-mbsync"&gt;brutal efficiency advantage of mbsync
over offlineimap&lt;/a&gt;, and having had delay issues with offlineimap myself, I
decided to try &lt;a class="reference external" href="https://sourceforge.net/projects/isync/"&gt;mbsync&lt;/a&gt; out (the project is actually called isync, but the
synchronization program is called mbsync).&lt;/p&gt;
&lt;p&gt;The main issue I saw with using mbsync is that of the three different emails
that I currently use, one of them had to use XOAUTH2 for authentication. This is
natively supported by offlineimap but not by mbsync.&lt;/p&gt;
&lt;div class="section" id="installation"&gt;
&lt;h2&gt;Installation&lt;/h2&gt;
&lt;p&gt;The first step of course was to install isync/mbsync.&lt;/p&gt;
&lt;p&gt;I then installed a SASL plugin for the XOAUTH2 mechanism, which in Arch Linux is
packaged in AUR as &lt;a class="reference external" href="https://aur.archlinux.org/packages/cyrus-sasl-xoauth2-git/"&gt;cyrus-sasl-xoauth2-git&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;For mbsync to authenticate through XOAUTH2, a program is needed to use the
account credentials to obtain the current token. For this I installed a python
package called oauth2token which can be installed with &lt;a class="reference external" href="https://pypi.org/project/oauth2token/"&gt;pip&lt;/a&gt;, or directly from
&lt;a class="reference external" href="https://aur.archlinux.org/packages/oauth2token/"&gt;AUR&lt;/a&gt;. Thanks to &lt;a class="reference external" href="https://github.com/VannTen"&gt;VannTen&lt;/a&gt; for creating this package and also for helping me solve
some issues with setting up mbsync with XOAUTH2!&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Note&lt;/strong&gt;: When I originally installed mbsync, there was an &lt;a class="reference external" href="https://sourceforge.net/p/isync/bugs/55/"&gt;issue&lt;/a&gt; with using it
with XOAUTH2. At the time of this writing though it was already fixed in version
1.3.2.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="configuration"&gt;
&lt;h2&gt;Configuration&lt;/h2&gt;
&lt;p&gt;To configure oauth2token I followed the instructions in the &lt;a class="reference external" href="https://github.com/VannTen/oauth2token/blob/master/README.rst"&gt;README&lt;/a&gt;, which
consist of basically creating two json files with the account information
(&lt;code class="docutils literal"&gt;client_id&lt;/code&gt; and &lt;code class="docutils literal"&gt;client_secret&lt;/code&gt; should be setup with the provider, in my
case Gmail) in a specific directory, executing &lt;code class="docutils literal"&gt;oauth2create &amp;lt;provider&amp;gt;
&amp;lt;account&amp;gt;&lt;/code&gt; and logging into the email account on the browser.&lt;/p&gt;
&lt;p&gt;Having set up oauth2token, configuring mbsync to use it was pretty simple. I
just added &lt;code class="docutils literal"&gt;PassCmd &amp;quot;oauth2get &amp;lt;provider&amp;gt; &amp;lt;account&amp;gt;&amp;quot;&lt;/code&gt; in the &lt;code class="docutils literal"&gt;IMAPAccount&lt;/code&gt;
section of my &lt;code class="docutils literal"&gt;.mbsyncrc&lt;/code&gt;, obviously changing &lt;code class="docutils literal"&gt;&amp;lt;provider&amp;gt;&lt;/code&gt; and &lt;code class="docutils literal"&gt;&amp;lt;account&amp;gt;&lt;/code&gt;
for the values I configured in oauth2token. In my case it wasn't necessary to
specify the authentication mechanism in that same section with &lt;code class="docutils literal"&gt;AuthMechs
XOAUTH2&lt;/code&gt;, since XOAUTH2 is considered the safest mechanism installed, so it is
the default. Rather I needed to use &lt;code class="docutils literal"&gt;AuthMechs PLAIN&lt;/code&gt; in the &lt;code class="docutils literal"&gt;IMAPAccount&lt;/code&gt;
section of the other accounts that I didn't want to use XOAUTH2 with.&lt;/p&gt;
&lt;p&gt;Finally, I ran mbsync for the account that I configured XOAUTH2 with (&amp;quot;dac&amp;quot;).
By making the output verbose (&lt;code class="docutils literal"&gt;-V&lt;/code&gt;), it can be seen that it authenticated
using XOAUTH2 and everything worked as expected:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
[nfraprado&amp;#64;ArchWay ~]$ mbsync -V dac
Reading configuration file /home/nfraprado/.mbsyncrc
C: 0/1  B: 0/0  M: +0/0 *0/0 #0/0  S: +0/0 *0/0 #0/0
Channel dac
Opening master store dac-remote...
Resolving imap.gmail.com... ok
Connecting to imap.gmail.com ([2800:3f0:4003:c02::6c]:993)...
Opening slave store dac-local...
Connection is now encrypted
Logging in...
Authenticating with SASL mechanism XOAUTH2...
C: 0/1  B: 0/5  M: +0/0 *0/0 #0/0  S: +0/0 *0/0 #0/0
Opening master box INBOX...
Opening slave box INBOX...
Maildir notice: no UIDVALIDITY, creating new.
Loading master...
master: 0 messages, 0 recent
Loading slave...
slave: 0 messages, 0 recent
Synchronizing...
C: 0/1  B: 1/5  M: +0/0 *0/0 #0/0  S: +0/0 *0/0 #0/0

[ ... ]

Opening master box _Sent...
Opening slave box _Sent...
Creating slave _Sent...
Maildir notice: no UIDVALIDITY, creating new.
Loading master...
Loading slave...
slave: 0 messages, 0 recent
master: 92 messages, 0 recent
Synchronizing...
C: 1/1  B: 5/5  M: +0/0 *0/0 #0/0  S: +367/367 *0/0 #0/0
&lt;/pre&gt;
&lt;/div&gt;
</content><category term="2020"/><category term="xoauth2"/><category term="mbsync"/><category term="email"/></entry><entry><title>Making an UART cable for the Nexus 5</title><link href="https://nfraprado.net/post/making-an-uart-cable-for-the-nexus-5.html" rel="alternate"/><published>2020-06-30T00:00:00-03:00</published><updated>2020-06-30T00:00:00-03:00</updated><author><name>Nícolas F. R. A. Prado</name></author><id>tag:nfraprado.net,2020-06-30:/post/making-an-uart-cable-for-the-nexus-5.html</id><summary type="html">&lt;p&gt;Recently, me and &lt;a class="reference external" href="https://andrealmeid.com/"&gt;a friend&lt;/a&gt; started digging into making the Nexus 5 run the
mainline Linux kernel. The purpose of this is, apart from a great learning
experience, to make the Nexus 5 run a Linux distro, like &lt;a class="reference external" href="https://postmarketos.org/"&gt;PostmarketOS&lt;/a&gt;, instead
of Android, while also having lifetime updates delivered from the …&lt;/p&gt;</summary><content type="html">&lt;p&gt;Recently, me and &lt;a class="reference external" href="https://andrealmeid.com/"&gt;a friend&lt;/a&gt; started digging into making the Nexus 5 run the
mainline Linux kernel. The purpose of this is, apart from a great learning
experience, to make the Nexus 5 run a Linux distro, like &lt;a class="reference external" href="https://postmarketos.org/"&gt;PostmarketOS&lt;/a&gt;, instead
of Android, while also having lifetime updates delivered from the mainline.&lt;/p&gt;
&lt;p&gt;Of course, the first thing before getting to coding is to get the UART from the
phone to the computer, so that I could read the log messages from early boot of
the kernel and figure out the cause of any issue. In the case of the Nexus 5,
the UART is on the audio jack, so a way to connect it to the USB of my computer
was needed.&lt;/p&gt;
&lt;p&gt;I found instructions about the construction of this cable on both the
&lt;a class="reference external" href="https://wiki.postmarketos.org/wiki/Serial_debugging:Cable_schematics#Nexus_debug_cable"&gt;postmarketOS wiki&lt;/a&gt; and on the &lt;a class="reference external" href="https://masneyb.github.io/nexus-5-upstream/UART_CABLE.html"&gt;nexus-5-upstream website&lt;/a&gt;, which is a project from Brian Masney, who already contributed a
lot of patches to upstreaming the Nexus 5.&lt;/p&gt;
&lt;p&gt;The information on these pages were conflicting, so I figured it would be best
to prototype the circuit first and make sure it worked before soldering it.&lt;/p&gt;
&lt;div class="section" id="prototyping"&gt;
&lt;h2&gt;Prototyping&lt;/h2&gt;
&lt;p&gt;I had to prototype using only the components I already had, since I would
already buy some components for the final board, and didn't want to do two
purchases.&lt;/p&gt;
&lt;p&gt;I started following the schematic on the &lt;a class="reference external" href="https://wiki.postmarketos.org/wiki/Serial_debugging:Cable_schematics#Nexus_debug_cable"&gt;postmarketOS wiki&lt;/a&gt;,
since it was based on a schematic from Google.&lt;/p&gt;
&lt;p&gt;For the audio jack I used an old earphone I had around, and cut it so that I
ended up with only the cable with the wires exposed and the connector. I
followed the same resistor values on the schematic, using a 1kΩ and two 100Ω
resistors in series to get the 1.2kΩ for the voltage divider on the TX pin.&lt;/p&gt;
&lt;p&gt;To bridge UART to USB I used the &lt;a class="reference external" href="https://www.adafruit.com/product/2264"&gt;Adafruit FT232H breakout&lt;/a&gt; I had. Since it
didn't have a 3.3V output, I tried some combination of resistors to divide the
5V. That got me to 3.26V but it still didn't work.&lt;/p&gt;
&lt;p&gt;I was almost giving up, but remembered that Brian Masney wrote in that page
that it &lt;a class="reference external" href="https://masneyb.github.io/nexus-5-upstream/UART_CABLE.html"&gt;really needs to be at 3.3V&lt;/a&gt;, and I also remembered
I had an SPI to USB converter board that had a 3.3V regulated output. I decided
to try it out.&lt;/p&gt;
&lt;p&gt;And after only a minor hiccup, with having to change the baud rate to 115200,
what do you know, it worked! 🥳&lt;/p&gt;
&lt;p&gt;This is how the prototype looked:&lt;/p&gt;
&lt;img alt="Audio jack &amp;lt;-&amp;gt; UART &amp;lt;-&amp;gt; USB prototype" src="/images/nexus5-uart/prototype.jpg" /&gt;
&lt;p&gt;I know, it looks horrible, but hey, it works!&lt;/p&gt;
&lt;p&gt;Having got it to work I ordered some more parts to make the circuit neater.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="final-board"&gt;
&lt;h2&gt;Final board&lt;/h2&gt;
&lt;p&gt;The components I used to build the board were:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;wires&lt;/li&gt;
&lt;li&gt;1 universal PCB (at least 9x7 holes)&lt;/li&gt;
&lt;li&gt;1 1kΩ resistor&lt;/li&gt;
&lt;li&gt;1 1.2kΩ resistor&lt;/li&gt;
&lt;li&gt;1 1x7 male pin header&lt;/li&gt;
&lt;li&gt;1 TRRS audio jack cable (from an old earphone) Note: it &lt;em&gt;really&lt;/em&gt; needs to be
a TRRS, that is, to have &lt;a class="reference external" href="https://en.wikipedia.org/wiki/Phone_connector_(audio)#/media/File:3.5mm.jpg"&gt;4 separate conductors&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;1 &lt;a class="reference external" href="https://www.multcomercial.com.br/placa-ftdi-ft232rl-conversor-usb-serial-arduino-gc-54.html"&gt;UART &amp;lt;-&amp;gt; USB converter&lt;/a&gt; (from a store in Brazil, but any with 3.3V is
fine)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;For reference, this is how the pins should be connected (but you really should
refer to the &lt;a class="reference external" href="https://wiki.postmarketos.org/wiki/Serial_debugging:Cable_schematics#Nexus_debug_cable"&gt;schematic at postmarketOS&lt;/a&gt;):&lt;/p&gt;
&lt;table border="1" class="docutils"&gt;
&lt;colgroup&gt;
&lt;col width="19%" /&gt;
&lt;col width="50%" /&gt;
&lt;col width="31%" /&gt;
&lt;/colgroup&gt;
&lt;thead valign="bottom"&gt;
&lt;tr&gt;&lt;th class="head"&gt;UART pin&lt;/th&gt;
&lt;th class="head"&gt;Between&lt;/th&gt;
&lt;th class="head"&gt;Audio jack pin&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody valign="top"&gt;
&lt;tr&gt;&lt;td&gt;RX&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;Tip (TX)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;TX (at 3.3V)&lt;/td&gt;
&lt;td&gt;Voltage divider (1kΩ and 1.2kΩ)&lt;/td&gt;
&lt;td&gt;Ring 1 (RX at 1.8V)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;GND&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;Ring 2&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;3.3V&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;Sleeve&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;I invested a little time thinking how was the most neat way I could solder the
connections and resistors to the board, and I ended up with this:&lt;/p&gt;
&lt;img alt="Connections on the PCB" src="/images/nexus5-uart/lower.jpg" /&gt;
&lt;p&gt;And with the connections on the back side and corresponding pins shown:&lt;/p&gt;
&lt;img alt="Connections on the PCB annotated" src="/images/nexus5-uart/lower_annotated.jpg" /&gt;
&lt;p&gt;My PCB was bigger than the converter board so I sawed it to have approximately
the same size (17x7), but the connections only take up a 9x7.&lt;/p&gt;
&lt;p&gt;I also changed the jumper position on the UART to USB converter board so that
VCC was 3.3V.&lt;/p&gt;
&lt;p&gt;Finally I soldered the male pin headers to the converter board, and that on top
of my PCB, ending up with a pretty cool 2-story board 😎.&lt;/p&gt;
&lt;p&gt;This is how the final board looks:&lt;/p&gt;
&lt;img alt="Final board top view" src="/images/nexus5-uart/final_top.jpg" /&gt;
&lt;img alt="Final board side view" src="/images/nexus5-uart/final_side.jpg" /&gt;
&lt;/div&gt;
&lt;div class="section" id="testing"&gt;
&lt;h2&gt;Testing&lt;/h2&gt;
&lt;p&gt;After connecting the board's USB to my computer and the audio jack to the Nexus
5, I opened a serial console using &lt;code class="docutils literal"&gt;picocom&lt;/code&gt; with a 115200 baud rate:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;picocom&lt;span class="w"&gt; &lt;/span&gt;/dev/ttyUSB0&lt;span class="w"&gt; &lt;/span&gt;-b&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;115200&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;And booted the Nexus 5 into fastboot mode by holding the power and volume down
buttons, to be greeted with this heartwarming message:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
welcome to hammerhead bootloader
[10] Power on reason 80
[10] DDR: hynix
[110] Loaded IMGDATA at 0x11000000
[110] Display Init: Start
[190] MDP GDSC already enabled
[190] bpp 24
[230] Config MIPI_CMD_PANEL.
[230] display panel: ORISE
[230] display panel: Default setting
[360] Turn on MIPI_CMD_PANEL.
[410] Display Init: Done
[410] cable type from shared memory: 8
[410] vibe
[610] USB init ept &amp;#64; 0xf96b000
[630] secured device: 1
[630] fastboot_init()
[680] splash: fastboot_op
 FASTBOOT MODE
 PRODUCT_NAME - hammerhead
 VARIANT - hammerhead D821(H) 16GB
 HW VERSION - rev_11
 BOOTLOADER VERSION - HHZ20h
 BASEBAND VERSION - M8974A-2.0.50.2.30
 CARRIER INFO - None
 SERIAL NUMBER - ***
 SIGNING - production
 SECURE BOOT - enabled
 LOCK STATE - unlocked
[790] splash: start
[1820] Fastboot mode started
[1820] udc_start()
&lt;/pre&gt;
&lt;p&gt;Since I verified that the pinout on &lt;a class="reference external" href="https://masneyb.github.io/nexus-5-upstream/UART_CABLE.html"&gt;nexus-5-upstream&lt;/a&gt; was
wrong, I sent a &lt;a class="reference external" href="https://github.com/masneyb/nexus-5-upstream/pull/4"&gt;pull request&lt;/a&gt; fixing it, so there's no such confusion anymore
😉.&lt;/p&gt;
&lt;p&gt;Great! Now that I can read all boot logs I'm ready to dive into the kernel code.
Although I'm sure this was the easiest part of the project 😅...&lt;/p&gt;
&lt;/div&gt;
</content><category term="2020"/><category term="nexus5"/><category term="electronics"/></entry><entry><title>Creating movie and game lists using Taskwarrior</title><link href="https://nfraprado.net/post/creating-movie-and-game-lists-using-taskwarrior.html" rel="alternate"/><published>2020-05-25T00:00:00-03:00</published><updated>2020-05-25T00:00:00-03:00</updated><author><name>Nícolas F. R. A. Prado</name></author><id>tag:nfraprado.net,2020-05-25:/post/creating-movie-and-game-lists-using-taskwarrior.html</id><summary type="html">&lt;p&gt;I like watching movies and playing games, but I also like to keep track of which
movies I've watched and which games I've beaten. I also got into the habit of
rating each movie I watch. For the longest time, though, I have kept track of
these collections informally in …&lt;/p&gt;</summary><content type="html">&lt;p&gt;I like watching movies and playing games, but I also like to keep track of which
movies I've watched and which games I've beaten. I also got into the habit of
rating each movie I watch. For the longest time, though, I have kept track of
these collections informally in my memory or scattered in text files on my
computer.&lt;/p&gt;
&lt;p&gt;A couple years ago I started trying to keep myself more focused and organized by
using a &lt;em&gt;CLI&lt;/em&gt; (Command-line interface) TODO list manager called &lt;a class="reference external" href="https://taskwarrior.org/"&gt;Taskwarrior&lt;/a&gt;.
Suddenly I realized that I could also use this awesome tool to keep lists for my
movies and games!&lt;/p&gt;
&lt;div class="section" id="taskwarrior-configuration"&gt;
&lt;h2&gt;Taskwarrior configuration&lt;/h2&gt;
&lt;p&gt;If you know Taskwarrior, you know that it just needs a configuration
file (normally a &lt;code class="docutils literal"&gt;.taskrc&lt;/code&gt; in your home folder), and a folder to keep its data
files.&lt;/p&gt;
&lt;p&gt;In order to keep these lists separate from my main Taskwarrior tasks, I created
a directory (&lt;code class="docutils literal"&gt;/home/nfraprado/txt/todo&lt;/code&gt;) to keep a separate configuration file
(&lt;code class="docutils literal"&gt;config&lt;/code&gt;) and data folder (&lt;code class="docutils literal"&gt;data&lt;/code&gt;).&lt;/p&gt;
&lt;p&gt;Inside &lt;code class="docutils literal"&gt;config&lt;/code&gt;, I first defined the data folder:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="na"&gt;data.location&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;/home/nfraprado/txt/todo/data&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;I then removed color from tagged tasks and made search case insensitive:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="na"&gt;color.tagged&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;none&lt;/span&gt;

&lt;span class="na"&gt;search.case.sensitive&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;no&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;And also created a new &lt;em&gt;UDA&lt;/em&gt;, or User Defined Attribute, called Score, so that I
could add a score to a movie after I watched it:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="na"&gt;uda.score.type&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;numeric&lt;/span&gt;
&lt;span class="na"&gt;uda.score.label&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;Score&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Finally, I added the movies and games reports. Each report can have its filter
configured to determine which tasks are shown, and its columns, to determine
which task attributes are displayed, as well as some other configurations.&lt;/p&gt;
&lt;p&gt;For the games, I wanted two reports: one called &lt;code class="docutils literal"&gt;games.todo&lt;/code&gt;, to show which
games I still need to beat, and another called &lt;code class="docutils literal"&gt;games.done&lt;/code&gt; for the games I've
already beaten:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="c1"&gt;# Games reports&lt;/span&gt;
&lt;span class="na"&gt;report.games.todo.description&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;Games to complete&lt;/span&gt;
&lt;span class="na"&gt;report.games.todo.labels&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;ID,Title&lt;/span&gt;
&lt;span class="na"&gt;report.games.todo.columns&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;id,description&lt;/span&gt;
&lt;span class="na"&gt;report.games.todo.filter&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;status:pending limit:page project:games&lt;/span&gt;

&lt;span class="na"&gt;report.games.done.description&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;Games completed&lt;/span&gt;
&lt;span class="na"&gt;report.games.done.labels&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;Title,Completed on&lt;/span&gt;
&lt;span class="na"&gt;report.games.done.columns&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;description,end&lt;/span&gt;
&lt;span class="na"&gt;report.games.done.filter&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;status:completed limit:page project:games&lt;/span&gt;
&lt;span class="na"&gt;report.games.done.sort&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;end-&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;For the movies, I also added a &lt;code class="docutils literal"&gt;todo&lt;/code&gt; and a &lt;code class="docutils literal"&gt;done&lt;/code&gt; report, but the &lt;code class="docutils literal"&gt;done&lt;/code&gt;
report also has a score column so that I can see the score that I gave to each
movie. I also added a &lt;code class="docutils literal"&gt;movies.rank&lt;/code&gt; report for the movies that is sorted based
on the score, so I can have a list of my favorite movies 🙂:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="c1"&gt;# Movies reports&lt;/span&gt;
&lt;span class="na"&gt;report.movies.todo.description&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;Movies to watch&lt;/span&gt;
&lt;span class="na"&gt;report.movies.todo.labels&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;ID,Tags,Title&lt;/span&gt;
&lt;span class="na"&gt;report.movies.todo.columns&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;id,tags,description&lt;/span&gt;
&lt;span class="na"&gt;report.movies.todo.filter&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;status:pending limit:page project:movies&lt;/span&gt;

&lt;span class="na"&gt;report.movies.done.description&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;Movies seen&lt;/span&gt;
&lt;span class="na"&gt;report.movies.done.labels&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;Title,Tags,Score,Watched on&lt;/span&gt;
&lt;span class="na"&gt;report.movies.done.columns&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;description,tags,score,end&lt;/span&gt;
&lt;span class="na"&gt;report.movies.done.filter&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;status:completed limit:page project:movies&lt;/span&gt;
&lt;span class="na"&gt;report.movies.done.sort&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;end-&lt;/span&gt;

&lt;span class="na"&gt;report.movies.rank.description&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;Movies ranking&lt;/span&gt;
&lt;span class="na"&gt;report.movies.rank.labels&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;Title,Tags,Score&lt;/span&gt;
&lt;span class="na"&gt;report.movies.rank.columns&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;description,tags,score&lt;/span&gt;
&lt;span class="na"&gt;report.movies.rank.filter&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;status:completed limit:page project:movies&lt;/span&gt;
&lt;span class="na"&gt;report.movies.rank.sort&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;score-&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="bash-configuration"&gt;
&lt;h2&gt;Bash configuration&lt;/h2&gt;
&lt;p&gt;With Taskwarrior configured, I just needed some aliases in my &lt;code class="docutils literal"&gt;.bashrc&lt;/code&gt; to
make it convenient to add, complete and see the movies and games.&lt;/p&gt;
&lt;p&gt;Inside my &lt;code class="docutils literal"&gt;.bashrc&lt;/code&gt;, I first added a variable pointing to the config location:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="nv"&gt;TODOCONFIG&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="nv"&gt;$HOME&lt;/span&gt;&lt;span class="s2"&gt;/txt/todo/config&amp;quot;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Then I added aliases to add, list and list done for both games and movies. Also
an alias to mark a game as completed and one to see the movies ranking. For all
these commands, I used &lt;code class="docutils literal"&gt;rc:$TODOCONFIG&lt;/code&gt; to make Taskwarrior use the
configuration file that I previously added. For the add and done aliases I just
use the &lt;code class="docutils literal"&gt;add&lt;/code&gt; and &lt;code class="docutils literal"&gt;done&lt;/code&gt; Taskwarrior commands, respectively. Listing is done
by just calling the previously written reports:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="c1"&gt;# Games list&lt;/span&gt;
&lt;span class="nb"&gt;alias&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;gamadd&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;task rc:$TODOCONFIG add project:games&amp;#39;&lt;/span&gt;
&lt;span class="nb"&gt;alias&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;gamlist&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;task rc:$TODOCONFIG games.todo&amp;#39;&lt;/span&gt;
&lt;span class="nb"&gt;alias&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;gamlistdone&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;task rc:$TODOCONFIG games.done&amp;#39;&lt;/span&gt;
&lt;span class="nb"&gt;alias&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;gamdone&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;task rc:$TODOCONFIG done&amp;#39;&lt;/span&gt;

&lt;span class="c1"&gt;# Movies list&lt;/span&gt;
&lt;span class="nb"&gt;alias&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;movadd&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;task rc:$TODOCONFIG add project:movies&amp;#39;&lt;/span&gt;
&lt;span class="nb"&gt;alias&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;movlist&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;task rc:$TODOCONFIG movies.todo&amp;#39;&lt;/span&gt;
&lt;span class="nb"&gt;alias&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;movlistdone&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;task rc:$TODOCONFIG movies.done&amp;#39;&lt;/span&gt;
&lt;span class="nb"&gt;alias&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;movrank&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;task rc:$TODOCONFIG movies.rank&amp;#39;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Completing a movie can't be done as a simple alias, since I also want to give it
a score, so I create a simple bash function that takes the task id and score,
applies the given score to the task and marks it as done:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;movdone&lt;span class="o"&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;if&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;-z&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="nv"&gt;$1&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&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="o"&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;-z&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="nv"&gt;$2&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="k"&gt;then&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Usage: movdone id score&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;fi&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;task&lt;span class="w"&gt; &lt;/span&gt;rc:&lt;span class="nv"&gt;$TODOCONFIG&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;modify&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="nv"&gt;$1&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;score:&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="nv"&gt;$2&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;task&lt;span class="w"&gt; &lt;/span&gt;rc:&lt;span class="nv"&gt;$TODOCONFIG&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;done&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="nv"&gt;$1&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="result"&gt;
&lt;h2&gt;Result&lt;/h2&gt;
&lt;p&gt;With this setup done, I could finally track my movies and games very easily.&lt;/p&gt;
&lt;p&gt;Adding a new movie to watch is as simples as &lt;code class="docutils literal"&gt;movadd movie_name&lt;/code&gt; (spaces can
actually be used in the name).&lt;/p&gt;
&lt;p&gt;And after watching a movie, I can easily mark it as completed and rate it with
&lt;code class="docutils literal"&gt;movdone id rating&lt;/code&gt;. The &lt;code class="docutils literal"&gt;id&lt;/code&gt; for the movie can be seen using &lt;code class="docutils literal"&gt;movlist&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Here are the movies that I plan to watch:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
[nfraprado&amp;#64;ArchWay ~]$ movlist
Using alternate .taskrc file /home/nfraprado/txt/todo/config

ID Title
23 Once Upon a Time In Hollywood
27 Ready player one
28 In The Shadow Of The Moon 2007
&lt;/pre&gt;
&lt;p&gt;The last 5 movies I've watched:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
[nfraprado&amp;#64;ArchWay ~]$ movlistdone
Using alternate .taskrc file /home/nfraprado/txt/todo/config

Title                                             Tags   Score Watched on
Arrival                                                      8 2020-05-25
The Green Mile                                               8 2020-05-17
The Boy Who Harnessed the Wind                               8 2020-04-25
Jojo Rabbit                                                  6 2020-04-18
Back to the Future                                           8 2020-04-12
&lt;/pre&gt;
&lt;p&gt;My highest rated movies (since I created this list):&lt;/p&gt;
&lt;pre class="literal-block"&gt;
[nfraprado&amp;#64;ArchWay ~]$ movrank
Using alternate .taskrc file /home/nfraprado/txt/todo/config

Title                                             Tags   Score
The Lord of the Rings: The Return of the King               10
The Lord of the Rings: The Two Towers                       10
The Lord of the Rings: The Fellowship of the Ring           10
Whisper of the Heart                              ghibli     9
Apollo 11                                         doc        9
Life is beautiful                                            9
Koe no katachi                                               9
&lt;/pre&gt;
&lt;p&gt;Some games I plan to beat:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
[nfraprado&amp;#64;ArchWay ~]$ gamlist
Using alternate .taskrc file /home/nfraprado/txt/todo/config

ID Title
 6 FTL
 7 The Witness
 8 Factorio
 9 Stardew Valley
10 Half Life
&lt;/pre&gt;
&lt;p&gt;And the last 5 games I've beaten:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
[nfraprado&amp;#64;ArchWay ~]$ gamlistdone
Using alternate .taskrc file /home/nfraprado/txt/todo/config

Title                                                      Completed on
The Stanley Parable                                        2020-04-24
Undertale                                                  2020-04-17
Cave Story                                                 2020-03-30
Antichamber                                                2020-03-22
Papers, Please                                             2020-03-19
&lt;/pre&gt;
&lt;/div&gt;
</content><category term="2020"/><category term="cli"/><category term="taskwarrior"/><category term="organization"/></entry></feed>