<?xml version="1.0" encoding="utf-8"?>
<?xml-stylesheet type="text/xsl" href="assets/xml/rss.xsl" media="all"?><rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Ralsina.Me</title><link>https://ralsina.me/</link><description>I write free software. I have an opinion on almost everything. I write quickly. A weblog was inevitable.</description><atom:link href="https://ralsina.me/rss.xml" rel="self" type="application/rss+xml"></atom:link><language>en</language><lastBuildDate>Thu, 01 Jun 2023 01:20:40 GMT</lastBuildDate><generator>Nikola (getnikola.com)</generator><docs>http://blogs.law.harvard.edu/tech/rss</docs><item><title>Learning Crystal by Implementing a Static Site Generator</title><link>https://ralsina.me/weblog/posts/learning-crystal-by-implementing-a-static-site-generator.html</link><dc:creator>Roberto Alsina</dc:creator><description>&lt;h3&gt;What?&lt;/h3&gt;

&lt;p&gt;A while back (10 YEARS???? WTH.) I wrote a stat­ic site gen­er­a­tor. I mean, I wrote one that is large and some­what pop­u­lar, called  &lt;a href="https://github.com/getnikola/nikola"&gt;Niko­la&lt;/a&gt;  but I al­so wrote a tiny one called  &lt;a href="https://github.com/ralsina/nicoletta"&gt;Nico­let­ta&lt;/a&gt;&lt;/p&gt; 
 &lt;p&gt;Why? Be­cause it's a nice lit­tle project and it shows the very ba­sics of how to do a whole projec­t.  &lt;/p&gt; 
 &lt;p&gt;All it does is:&lt;/p&gt; 
 &lt;ul&gt;
&lt;li&gt;Find mark­down files&lt;/li&gt;  &lt;li&gt;Build them&lt;/li&gt;  &lt;li&gt;Use tem­plates to gen­er­ate HTML files&lt;/li&gt;  &lt;li&gt;Put those in an out­put fold­er&lt;/li&gt;  &lt;/ul&gt;

&lt;p&gt;And that's it, that's a SS­G.&lt;/p&gt; 
 &lt;p&gt;So, if I want­ed a "toy" project to prac­tice new (to me) pro­gram­ming lan­guages, why not re­write that?&lt;/p&gt; 
 &lt;p&gt;And why not write about how it goes while I do it?&lt;/p&gt; 
 &lt;p&gt;Hence this.&lt;/p&gt; 
 &lt;h3&gt;So, what's Crystal?&lt;/h3&gt;

&lt;p&gt;It's (they say) "A lan­guage for hu­mans and com­put­er­s". In short: a com­piled, stat­i­cal­ly typed lan­guage with a ru­by flavoured syn­tax.&lt;/p&gt; 
 &lt;p&gt;And why? Again, why not?&lt;/p&gt; 
 &lt;h3&gt;Getting started&lt;/h3&gt;

&lt;p&gt;I in­stalled it us­ing  &lt;a href="https://crystal-lang.org/install/on_ubuntu/"&gt;curl&lt;/a&gt;  and that got me ver­sion 1.8.2 which is the lat­est at the time of writ­ing this.&lt;/p&gt; 
 &lt;p&gt;You can get your project start­ed by run­ning a com­mand:&lt;/p&gt; 
 &lt;pre&gt;&lt;code class="language-shell"&gt;nicoletta/crystal
✦ &amp;gt; crystal init app nicoletta .
    create  /home/ralsina/zig/nicoletta/crystal/.gitignore
    create  /home/ralsina/zig/nicoletta/crystal/.editorconfig
    create  /home/ralsina/zig/nicoletta/crystal/LICENSE
    create  /home/ralsina/zig/nicoletta/crystal/README.md
    create  /home/ralsina/zig/nicoletta/crystal/shard.yml
    create  /home/ralsina/zig/nicoletta/crystal/src/nicoletta.cr
    create  /home/ralsina/zig/nicoletta/crystal/spec/spec_helper.cr
    create  /home/ralsina/zig/nicoletta/crystal/spec/nicoletta_spec.cr
Initialized empty Git repository in /home/ralsina/zig/nicoletta/crystal/.git/
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Some maybe in­ter­est­ing bit­s:&lt;/p&gt; 
 &lt;ul&gt;
&lt;li&gt;It inits a git re­po, with a git­ig­nore in it&lt;/li&gt;  &lt;li&gt;Sets you up with a MIT li­cense&lt;/li&gt;  &lt;li&gt;Cre­ates a rea­son­able README with nice place­hold­ers&lt;/li&gt;  &lt;li&gt;We get a &lt;code&gt;shard.yml&lt;/code&gt;with metadata&lt;/li&gt;
&lt;li&gt;Source code in &lt;code&gt;src/&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;spec/&lt;/code&gt; seems to be for tests?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Mind you, I still have ze­ro idea about the lan­guage :-)&lt;/p&gt; 
 &lt;p&gt;This ap­par­ent­ly com­piles in­to a do-noth­ing pro­gram, which is ok. Sur­prisied to see  &lt;a href="https://starship.rs/"&gt;star­ship&lt;/a&gt;  seems to sup­port crys­tal in the promp­t!&lt;/p&gt; 
 &lt;pre&gt;&lt;code class="language-shell"&gt;crystal on  main [?] is 📦 v0.1.0 via 🔮 v1.8.2 
&amp;gt; crystal build src/nicoletta.cr

crystal on  main [?] is 📦 v0.1.0 via 🔮 v1.8.2 
&amp;gt; ls -l
total 1748
-rw-rw-r-- 1 ralsina ralsina    2085 may 31 18:15 journal.md
-rw-r--r-- 1 ralsina ralsina    1098 may 31 18:08 LICENSE
-rwxrwxr-x 1 ralsina ralsina 1762896 may 31 18:15 nicoletta*
-rw-r--r-- 1 ralsina ralsina     604 may 31 18:08 README.md
-rw-r--r-- 1 ralsina ralsina     167 may 31 18:08 shard.yml
drwxrwxr-x 2 ralsina ralsina    4096 may 31 18:08 spec/
drwxrwxr-x 2 ralsina ralsina    4096 may 31 18:08 src/
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Per­haps a bit sur­pris­ing that the do-noth­ing bi­na­ry is 1.7MB tho (1.2MB stripped) but it's just 380KB in "re­lease mod­e" which is nice.&lt;/p&gt; 
 &lt;h3&gt;Learning a Bit of Crystal&lt;/h3&gt;

&lt;p&gt;At this point I will stop and learn some syn­tax:&lt;/p&gt; 
 &lt;ul&gt;
&lt;li&gt;How to de­clare a vari­able / a lit­er­al / a con­stant&lt;/li&gt;  &lt;li&gt;How to do an if / loop&lt;/li&gt;  &lt;li&gt;How to de­fine / call a func­tion&lt;/li&gt;  &lt;/ul&gt;

&lt;p&gt;Be­cause you know, one  &lt;em&gt;has&lt;/em&gt;  to know at least that much 😁&lt;/p&gt; 
 &lt;p&gt;There seems to be a de­cent set of  &lt;a href="https://crystal-lang.org/reference/1.8/tutorials/basics/index.html"&gt;tu­to­ri­als at this lev­el.&lt;/a&gt;  let's see how it look­s.&lt;/p&gt; 
 &lt;p&gt;Good thing: this is valid Crys­tal:&lt;/p&gt; 
 &lt;pre&gt;&lt;code class="language-crystal"&gt;module Nicoletta
  VERSION = "0.1.0"

  😀 = "Hello world"
  puts 😀 
end
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Al­so nice that vari­ables can change type.&lt;/p&gt; 
 &lt;p&gt;Having the docs say integers are &lt;code&gt;int32&lt;/code&gt; and anything else is "for special use cases" is not great. &lt;code&gt;int32&lt;/code&gt; is small.&lt;/p&gt;

&lt;p&gt;Al­so not a huge fan of sep­a­rate un­signed type­s.&lt;/p&gt; 
 &lt;p&gt;I &lt;em&gt;hate&lt;/em&gt; the "spaceship operator" &lt;code&gt;&amp;lt;==&amp;gt;&lt;/code&gt; which "compares its operands and returns a value that is either zero (both operands are equal), a positive value (the first operand is bigger), or a negative value (the second operand is bigger)"  ... &lt;em&gt;hate&lt;/em&gt; it.&lt;/p&gt;

&lt;p&gt;Num­bers have named meth­od­s, which is nice. How­ev­er it ran­dom­ly shows some weird syn­tax that has not been seen be­fore. One of these is not like the oth­er­s:&lt;/p&gt; 
 &lt;pre&gt;&lt;code class="language-crystal"&gt;p! -5.abs,   # absolute value
  4.3.round, # round to nearest integer
  5.even?,   # odd/even check
  10.gcd(16) # greatest common divisor
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Or maybe the &lt;code&gt;?&lt;/code&gt; is just part of the method name? Who knows! Not me!&lt;/p&gt;

&lt;p&gt;Nice string in­ter­po­la­tion thingie.&lt;/p&gt; 
 &lt;pre&gt;&lt;code class="language-crystal"&gt;name = "Crystal"
puts "Hello #{name}"
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Why would anyone add an &lt;code&gt;underscore&lt;/code&gt; method to strings? That's just weird.&lt;/p&gt;

&lt;p&gt;Slices are reasonable, &lt;code&gt;whatever[x..y]&lt;/code&gt; uses negative indexes for "from the right".&lt;/p&gt;

&lt;p&gt;We have truthy val­ues, 0 is truthy, on­ly nil, false and null point­ers are fal­sy. Ok.&lt;/p&gt; 
 &lt;p&gt;I &lt;em&gt;strongly dislike&lt;/em&gt; using &lt;code&gt;unless&lt;/code&gt; as a keyword instead of &lt;code&gt;if&lt;/code&gt; with a negated condition. I consider that to be keyword proliferation and cutesy.&lt;/p&gt;

&lt;p&gt;Meth­ods sup­port over­load­ing. Ok.&lt;/p&gt; 
 &lt;p&gt;Ok, I know just enough Crys­tal to be slight­ly dan­ger­ous. Those feel like  &lt;strong&gt;good&lt;/strong&gt;  tu­to­ri­al­s. Short, to the point, give you enough rope to ... make some­thing with rope, or what­ev­er.&lt;/p&gt; 
 &lt;h3&gt;Learning a Bit More Crystal&lt;/h3&gt;

&lt;p&gt;So: er­rors? Class­es? Block­s? How?&lt;/p&gt; 
 &lt;p&gt;Class­es are  &lt;a href="https://crystal-lang.org/reference/1.8/syntax_and_semantics/new%2C_initialize_and_allocate.html"&gt;pret­ty straight­for­ward&lt;/a&gt;  ... ap­par­ent­ly they are a bit frowned up­on for per­for­mance rea­sons be­cause they are heap al­lo­cat­ed, but what­evs.&lt;/p&gt; 
 &lt;p&gt;In­her­i­tance with method over­load­ing is not my cup of tea but 🤷&lt;/p&gt; 
 &lt;p&gt;Exceptions are &lt;a href="https://crystal-lang.org/reference/1.8/syntax_and_semantics/exception_handling.html"&gt;pretty simple&lt;/a&gt; but &lt;code&gt;begin / rescue / else / ensure / end&lt;/code&gt;? Eek.&lt;/p&gt;

&lt;p&gt;Also, I find that variables have &lt;code&gt;nil&lt;/code&gt; type in the &lt;code&gt;ensure&lt;/code&gt; block confusing.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://crystal-lang.org/reference/1.8/syntax_and_semantics/requiring_files.html"&gt;Re­quir­ing files&lt;/a&gt;  is not go­ing to be a prob­lem.  &lt;/p&gt; 
 &lt;p&gt;&lt;a href="https://crystal-lang.org/reference/1.8/syntax_and_semantics/blocks_and_procs.html"&gt;Blocks&lt;/a&gt;  are in­ter­est­ing but I am not go­ing to try to use them yet.&lt;/p&gt; 
 &lt;h3&gt;Dinner Break&lt;/h3&gt;

&lt;p&gt;I will grab din­ner, and then try to im­ple­ment Nico­let­ta, some­how. I'll prob­a­bly fail 😅&lt;/p&gt; 
 &lt;h3&gt;Implementing Nicoletta&lt;/h3&gt;

&lt;p&gt;The code for nico­let­ta  &lt;a href="https://github.com/ralsina/nicoletta/blob/master/nicoletta.py"&gt;is not long&lt;/a&gt;  so this should be a piece of cake.&lt;/p&gt; 
 &lt;p&gt;No need to have a &lt;code&gt;main&lt;/code&gt; in Crystal. Things just are executed.&lt;/p&gt;

&lt;p&gt;First, I need a way to read the con­fig­u­ra­tion. It looks like this:&lt;/p&gt; 
 &lt;pre&gt;&lt;code class="language-yaml"&gt;TITLE: "Nicoletta Test Blog"
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;That is  &lt;em&gt;tech­ni­cal­ly YAML&lt;/em&gt;  so sure­ly there is a crys­tal thing to read it. In fac­t, it's in the stan­dard li­brary! This frag­ment work­s:&lt;/p&gt; 
 &lt;pre&gt;&lt;code class="language-crystal"&gt;require "yaml"

VERSION = "0.1.0"

tpl_data = File.open("conf") do |file|
  YAML.parse(file)
end
p! tpl_data
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;And when ex­e­cut­ed does this, which is cor­rec­t:&lt;/p&gt; 
 &lt;pre&gt;&lt;code class="language-sh"&gt;crystal on  main [!?] is 📦 v0.1.0 via 🔮 v1.8.2 
&amp;gt; crystal run src/nicoletta.cr
tpl_data # =&amp;gt; {"TITLE" =&amp;gt; "Nicoletta Test Blog"}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Looks like what I want to store this sort of da­ta is a  &lt;a href="https://crystal-lang.org/reference/1.8/syntax_and_semantics/literals/hash.html"&gt;Hash&lt;/a&gt;&lt;/p&gt; 
 &lt;p&gt;Next step: read tem­plates and put them in a hash in­dexed by path.&lt;/p&gt; 
 &lt;p&gt;Templates are files in &lt;code&gt;templates/&lt;/code&gt; which look like this:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;&amp;lt;h2&amp;gt;&amp;lt;a href="${link}"&amp;gt;${title}&amp;lt;/a&amp;gt;&amp;lt;/h2&amp;gt;
date: ${date}
&amp;lt;hr&amp;gt;
${text}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Of course the syn­tax will prob­a­bly have to change, but for now I don't care.&lt;/p&gt; 
 &lt;p&gt;To find all files in &lt;code&gt;templates&lt;/code&gt; I can apparently use &lt;a href="https://crystal-lang.org/api/1.8.2/Dir.html#glob%28%2Apatterns%3APath%7CString%2Cmatch_hidden%3Dfalse%2Cfollow_symlinks%3Dfalse%29%3AArray%28String%29-class-method"&gt;&lt;code&gt;Dir.glob&lt;/code&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And I swear I wrote this  &lt;em&gt;al­most&lt;/em&gt;  in the first at­temp­t:&lt;/p&gt; 
 &lt;pre&gt;&lt;code class="language-Crystal"&gt;# Load templates
templates = {} of String =&amp;gt; String
Dir.glob("templates/*.tmpl").each do |path|
  templates[path] = File.read(path)
end
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Next is iterating over all files in &lt;code&gt;posts/&lt;/code&gt; (which are meant to be markdown with YAML metadata on top) and do things with them.&lt;/p&gt;

&lt;p&gt;It­er­at­ing them is the same as be­fore (hey, this is  &lt;em&gt;nice&lt;/em&gt;)&lt;/p&gt; 
 &lt;pre&gt;&lt;code class="language-Crystal"&gt;Dir.glob("posts/*.md").each do |path|
  # Stuff
end
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;But I will need a &lt;code&gt;Post&lt;/code&gt; class and so on, so...&lt;/p&gt;

&lt;p&gt;Here is a &lt;code&gt;Post&lt;/code&gt; class that is initialized by a path, parses metadata and keeps the text. &lt;/p&gt;

&lt;pre&gt;&lt;code class="language-Crystal"&gt;class Post
  def initialize(path)
    contents = File.read(path)
    metadata, @text = contents.split("\n\n", 2)
    @metadata = YAML.parse(metadata)
  end
  @metadata : YAML::Any
  @text : String
end
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Next step is to give that class a method to parse the mark­down and con­vert it to HTM­L.&lt;/p&gt; 
 &lt;p&gt;I am  &lt;em&gt;not&lt;/em&gt;  im­ple­ment­ing that so I googled for a Crys­tal mark­down im­ple­men­ta­tion and found  &lt;a href="https://github.com/icyleaf/markd"&gt;markd&lt;/a&gt;  which is sad­ly aban­doned but looks ok.&lt;/p&gt; 
 &lt;p&gt;Using it is surprisingly painless thanks to Crystal's &lt;a href="https://crystal-lang.org/reference/1.8/man/shards/index.html"&gt;shards&lt;/a&gt; dependency manager. First, I added it to &lt;code&gt;shard.yml&lt;/code&gt;:&lt;/p&gt;

&lt;pre&gt;&lt;code class="language-yaml"&gt;dependencies:
  markd:
   github: icyleaf/markd
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Ran &lt;code&gt;shards install&lt;/code&gt;:&lt;/p&gt;

&lt;pre&gt;&lt;code class="language-sh"&gt;crystal on  main [!+?] is 📦 v0.1.0 via 🔮 v1.8.2 
&amp;gt; shards install
Resolving dependencies
Fetching https://github.com/icyleaf/markd.git
Installing markd (0.5.0)
Writing shard.lock
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Then added a &lt;code&gt;require "markd"&lt;/code&gt;, slapped this code in the &lt;code&gt;Post&lt;/code&gt; class and that's it:&lt;/p&gt;

&lt;pre&gt;&lt;code class="language-Crystal"&gt;  def html
    Markd.to_html(@text)
  end
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Here is the code to parse all the posts and hold them in an ar­ray:&lt;/p&gt; 
 &lt;pre&gt;&lt;code&gt;posts = [] of Post

Dir.glob("posts/*.md").each do |path|
  posts &amp;lt;&amp;lt; Post.new(path)
end
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Now I need a Crys­tal im­ple­men­ta­tion of some tem­plate lan­guage, some­thing like  &lt;a href="https://handlebarsjs.com/"&gt;han­dle­bars&lt;/a&gt;, I don't need much!&lt;/p&gt; 
 &lt;p&gt;The stan­dard li­brary has a tem­plate lan­guage called  &lt;a href="https://crystal-lang.org/api/1.8.2/ECR.html"&gt;ECR&lt;/a&gt;  which is pret­ty nice but it's com­pile-­time and I need this to be done in run­time. So googled and found ...  &lt;a href="https://github.com/jeromegn/kilt"&gt;Kilt&lt;/a&gt;&lt;/p&gt; 
 &lt;p&gt;I will use the  &lt;a href="https://github.com/MakeNowJust/crustache"&gt;crus­tache&lt;/a&gt;  vari­ant, which im­ple­ments the  &lt;a href="https://mustache.github.io/"&gt;Mus­tache&lt;/a&gt;  stan­dard.&lt;/p&gt; 
 &lt;p&gt;Again, added the dependency to &lt;code&gt;shard.yml&lt;/code&gt; and ran &lt;code&gt;shards install&lt;/code&gt;:&lt;/p&gt;

&lt;pre&gt;&lt;code class="language-yaml"&gt;dependencies:
  markd:
   github: icyleaf/markd
  crustache:
   github: MakeNowJust/crustache
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Af­ter some refac­tor­ing of tem­plate code, the tem­plate load­er now looks like this:&lt;/p&gt; 
 &lt;pre&gt;&lt;code class="language-Crystal"&gt;class Template
  @text : String
  @compiled : Crustache::Syntax::Template

  def initialize(path)
    @text = File.read(path)
    @compiled = Crustache.parse(@text)
  end
end

# Load templates
templates = {} of String =&amp;gt; Template

Dir.glob("templates/*.tmpl").each do |path|
  templates[path] = Template.new(path)
end
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;I changed the tem­plates from what­ev­er they were be­fore to mus­tache:&lt;/p&gt; 
 &lt;pre&gt;&lt;code class="language-html"&gt;&amp;lt;h2&amp;gt;&amp;lt;a href="{{link}}"&amp;gt;{{title}}&amp;lt;/a&amp;gt;&amp;lt;/h2&amp;gt;
date: {{date}}
&amp;lt;hr&amp;gt;
{{text}}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;I can now implement &lt;code&gt;Post.render&lt;/code&gt;... except that top-level variables like &lt;code&gt;templates&lt;/code&gt; are not accessible from inside classes and that messes up my code, so it needs refactoring. So.&lt;/p&gt;

&lt;p&gt;This sure as hell is not id­iomat­ic Crys­tal, but bear with me, I am a be­gin­ner here.&lt;/p&gt; 
 &lt;p&gt;This scans for all posts, then prints them rendered with the &lt;code&gt;post.tmpl&lt;/code&gt; template:&lt;/p&gt;

&lt;pre&gt;&lt;code class="language-Crystal"&gt;class Post
  @metadata = {} of YAML::Any =&amp;gt; YAML::Any
  @text : String
  @link : String
  @html : String

  def initialize(path)
    contents = File.read(path)
    metadata, @text = contents.split("\n\n", 2)
    @metadata = YAML.parse(metadata).as_h
    @link = path.split("/")[-1][0..-4] + ".html"
    @html = Markd.to_html(@text)
  end

  def render(template)
    Crustache.render template.@compiled, @metadata.merge({"link" =&amp;gt; @link, "text" =&amp;gt; @html})
  end
end

posts = [] of Post

Dir.glob("posts/*.md").each do |path|
  posts &amp;lt;&amp;lt; Post.new(path)
  p! p.render templates["templates/post.tmpl"]
end
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Believe it or not, this is almost done.
Now I need to make it output that (passed through another template) into the right path in a &lt;code&gt;output/&lt;/code&gt; folder.&lt;/p&gt;

&lt;p&gt;This  &lt;strong&gt;al­most&lt;/strong&gt;  work­s:&lt;/p&gt; 
 &lt;pre&gt;&lt;code class="language-Crystal"&gt;Dir.glob("posts/*.md").each do |path|
  post = Post.new(path)
  rendered_post = post.render templates["templates/post.tmpl"]
  rendered_page = Crustache.render(templates["templates/page.tmpl"].@compiled,
    tpl_data.merge({
      "content" =&amp;gt; rendered_post,
    }))
  File.open("output/#{post.@link}", "w") do |io|
    io.puts rendered_page
  end
end
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;For some rea­son all my HTML is es­caped, I think that's the tem­plate en­gine try­ing to be safe 😤&lt;/p&gt; 
 &lt;p&gt;Turns out I had to use TRIPLE han­dle­bars to print un­escaped HTM­L, so af­ter a small fix in the tem­plates...  &lt;/p&gt; 
 &lt;p&gt;&lt;img src="https://i.imgur.com/HjvL0Y4.png" alt="A small HTML page"&gt;&lt;/p&gt; 
 &lt;p&gt;So, suc­cess! It has been fun, and I quite like the lan­guage!&lt;/p&gt; 
 &lt;p&gt;I pub­lished it at  &lt;a href="https://git.ralsina.me/ralsina/Nicoletta-Crystal"&gt;my git serv­er&lt;/a&gt;  but here's the full source code, all 60 lines of it:&lt;/p&gt; 
 &lt;pre&gt;&lt;code class="language-Crystal"&gt;# Nicoletta, a minimal static site generator.

require "yaml"
require "markd"
require "crustache"

VERSION = "0.1.0"

# Load config file
tpl_data = File.open("conf") do |file|
  YAML.parse(file).as_h
end

class Template
  @text : String
  @compiled : Crustache::Syntax::Template

  def initialize(path)
    @text = File.read(path)
    @compiled = Crustache.parse(@text)
  end
end

# Load templates
templates = {} of String =&amp;gt; Template

Dir.glob("templates/*.tmpl").each do |path|
  templates[path] = Template.new(path)
end

class Post
  @metadata = {} of YAML::Any =&amp;gt; YAML::Any
  @text : String
  @link : String
  @html : String

  def initialize(path)
    contents = File.read(path)
    metadata, @text = contents.split("\n\n", 2)
    @metadata = YAML.parse(metadata).as_h
    @link = path.split("/")[-1][0..-4] + ".html"
    @html = Markd.to_html(@text)
  end

  def render(template)
    Crustache.render template.@compiled, @metadata.merge({"link" =&amp;gt; @link, "text" =&amp;gt; @html})
  end
end

Dir.glob("posts/*.md").each do |path|
  post = Post.new(path)
  rendered_post = post.render templates["templates/post.tmpl"]
  rendered_page = Crustache.render(templates["templates/page.tmpl"].@compiled,
    tpl_data.merge({
      "content" =&amp;gt; rendered_post,
    }))
  File.open("output/#{post.@link}", "w") do |io|
    io.puts rendered_page
  end
end
&lt;/code&gt;&lt;/pre&gt;</description><category>crystal</category><category>nicoletta</category><category>nikola</category><category>programming</category><category>python</category><category>static site</category><guid>https://ralsina.me/weblog/posts/learning-crystal-by-implementing-a-static-site-generator.html</guid><pubDate>Thu, 01 Jun 2023 01:15:30 GMT</pubDate></item><item><title>Keeper of Enchanted Rooms (Whimbrel House, #1)</title><link>https://ralsina.me/weblog/posts/keeper-of-enchanted-rooms-whimbrel-house-1.html</link><dc:creator>Roberto Alsina</dc:creator><description>&lt;div&gt;
&lt;table&gt;
&lt;tr&gt;
&lt;td valign="top"&gt;
&lt;img class="img-thumbnail" style="width:10vw;" src="https://i.gr-assets.com/images/S/compressed.photo.goodreads.com/books/1653654975l/60596112._SY475_.jpg" width="10vw" alt="Cover for Keeper of Enchanted Rooms (Whimbrel House, #1)"&gt;
&lt;/td&gt;
&lt;td valign="top"&gt;
&lt;div class="panel"&gt;
&lt;ul class="list-group"&gt;
&lt;li class="list-group-item"&gt;  &lt;b&gt;Au­thor:&lt;/b&gt;  Char­lie N. Holm­berg &lt;/li&gt;&lt;li class="list-group-item"&gt;  &lt;b&gt;Rat­ing:&lt;/b&gt; 
★
★
★ &lt;/li&gt;&lt;li class="list-group-item"&gt;  &lt;a href="https://www.goodreads.com/review/show/5536565184?utm_medium=api&amp;amp;utm_source=rss"&gt;See in goodreads&lt;/a&gt;  &lt;/li&gt;&lt;/ul&gt;
&lt;h3&gt;Review:&lt;/h3&gt;
&lt;p&gt;Fun sto­ry. While it's about mag­ic and it has a sort of mag­ic sys­tem it feels more like a late 19th cen­tu­ry nov­el of man­ner­s, with just some mi­nor mag­i­cal el­e­ments in it.&lt;br&gt;&lt;br&gt;If you like that kind of thing, then this is your kind of thing?&lt;/p&gt;  &lt;/div&gt;
&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;</description><category>books</category><category>goodreads</category><guid>https://ralsina.me/weblog/posts/keeper-of-enchanted-rooms-whimbrel-house-1.html</guid><pubDate>Wed, 31 May 2023 20:35:10 GMT</pubDate></item><item><title>Agent of the Terran Empire (Flandry, #3)</title><link>https://ralsina.me/weblog/posts/agent-of-the-terran-empire-flandry-3.html</link><dc:creator>Roberto Alsina</dc:creator><description>&lt;div&gt;
&lt;table&gt;
&lt;tr&gt;
&lt;td valign="top"&gt;
&lt;img class="img-thumbnail" style="width:10vw;" src="https://i.gr-assets.com/images/S/compressed.photo.goodreads.com/books/1177593495l/715300.jpg" width="10vw" alt="Cover for Agent of the Terran Empire (Flandry, #3)"&gt;
&lt;/td&gt;
&lt;td valign="top"&gt;
&lt;div class="panel"&gt;
&lt;ul class="list-group"&gt;
&lt;li class="list-group-item"&gt;  &lt;b&gt;Au­thor:&lt;/b&gt;  Poul An­der­son &lt;/li&gt;&lt;li class="list-group-item"&gt;  &lt;b&gt;Rat­ing:&lt;/b&gt; 
★
★ &lt;/li&gt;&lt;li class="list-group-item"&gt;  &lt;a href="https://www.goodreads.com/review/show/5543091585?utm_medium=api&amp;amp;utm_source=rss"&gt;See in goodreads&lt;/a&gt;  &lt;/li&gt;&lt;/ul&gt;
&lt;h3&gt;Review:&lt;/h3&gt;
&lt;p&gt;I had fond mem­o­ries of read­ing An­der­son and oth­er clas­sic Sci­Fi of the same vin­tage when I was a kid.&lt;br&gt;&lt;br&gt;Well, that seems to be the right age. On one hand, a kid is apt to be car­ried away by the ad­ven­ture, which is fun, while al­so ig­nor­ing the mis­oginy, the ca­su­al chau­vin­is­m, the jin­go­is­m, the gen­er­al metaphor­i­cal racism and a whole lot of oth­er isms and in­ys which, as a grown up, re­al­ly dull the en­joy­ment of the ex­pe­ri­ence.&lt;/p&gt;  &lt;/div&gt;
&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;</description><category>books</category><category>goodreads</category><guid>https://ralsina.me/weblog/posts/agent-of-the-terran-empire-flandry-3.html</guid><pubDate>Fri, 19 May 2023 17:19:11 GMT</pubDate></item><item><title>New minisite: book covers</title><link>https://ralsina.me/weblog/posts/new-minisite-book-covers.html</link><dc:creator>Roberto Alsina</dc:creator><description>&lt;p&gt;Since I wrote  &lt;a href="https://github.com/ralsina/tapita"&gt;tapi­ta&lt;/a&gt;  to au­to­mat­i­cal­ly
cre­ate book cov­er­s, it was ab­surd­ly easy to turn it in­to a site where &lt;em&gt;you&lt;/em&gt;  can cre­ate book cov­er­s.&lt;/p&gt; 
 &lt;p&gt;So, you can go to  &lt;a href="https://covers.ralsina.me"&gt;Cov­er­s.ralsi­na.me&lt;/a&gt;  and 
cre­ate book cov­er­s.&lt;/p&gt; 
 &lt;p&gt;Fun part: this is the whole back­end for the site:&lt;/p&gt; 
 &lt;pre&gt;&lt;code class="language-python"&gt;from json import loads
from tapita import Cover
from io import BytesIO
import base64


def handle(req):
    """handle a request to the function
    Args:
        req (str): request body

    {
        "title": "foo",
        "subtitle": "bar",
        "author": "bat",
    }
    """
    try:
        args = loads(req)
    except Exception:
        return "Bad Request", 400

    c = Cover(**args)
    byte_arr = BytesIO()
    c.image.save(byte_arr, format="JPEG")

    return (
        f'&amp;lt;img src="data:image/jpeg;base64, {base64.b64encode(byte_arr.getvalue()).decode("utf-8")}"&amp;gt;',
        200,
        {"Content-Type": "text/html"},
&lt;/code&gt;&lt;/pre&gt;</description><category>book</category><category>covers</category><category>faas</category><category>python</category><category>tapita</category><guid>https://ralsina.me/weblog/posts/new-minisite-book-covers.html</guid><pubDate>Tue, 16 May 2023 15:55:21 GMT</pubDate></item><item><title>New project: Tapita</title><link>https://ralsina.me/weblog/posts/new-project-tapita.html</link><dc:creator>Roberto Alsina</dc:creator><description>&lt;p&gt;For rea­sons I won't dis­cuss, I need­ed to cre­ate a bunch of ebook cov­er­s.
Look­ing around I found some code to do it pro­gram­mat­i­cal­ly, so I 
adapt­ed/rewrote it and now pub­lished my ver­sion.&lt;/p&gt; 
 &lt;p&gt;More in­for­ma­tion at the  &lt;a href="https://github.com/ralsina/tapita"&gt;tapi­ta github re­po&lt;/a&gt;&lt;/p&gt; 
 &lt;p&gt;And here's an ex­am­ple cov­er:&lt;/p&gt; 
 &lt;p&gt;&lt;img src="https://i.imgur.com/UcjdkkN.jpg" style="max-width:70%; border: solid 1px #aaa; padding: 1em;"&gt;&lt;/p&gt; 
 &lt;p&gt;It does enough for what I need, but hap­py to work a bit more on it if any­one 
wants it.&lt;/p&gt;</description><category>ebooks</category><category>python</category><category>tapita</category><guid>https://ralsina.me/weblog/posts/new-project-tapita.html</guid><pubDate>Tue, 16 May 2023 00:44:18 GMT</pubDate></item><item><title>Home Server Update May 2023</title><link>https://ralsina.me/weblog/posts/home-server-update-may-2023.html</link><dc:creator>Roberto Alsina</dc:creator><description>&lt;p&gt;This is a longer-term up­date on the state of my home serv­er. You can read more about it
in these  &lt;a href="http://ralsina.me/weblog/posts/owning-a-pet-server-in-2022.html"&gt;1&lt;/a&gt;   &lt;a href="http://ralsina.me/weblog/posts/owning-a-pet-server-in-2022-part-2.html#"&gt;2&lt;/a&gt;   &lt;a href="http://ralsina.me/weblog/posts/the-cases-i-built-for-my-mini-servers.html"&gt;3&lt;/a&gt;   &lt;a href="http://ralsina.me/weblog/posts/radxa-zero.html"&gt;4&lt;/a&gt;   &lt;a href="https://ralsina.me/weblog/posts/pet-server-september-2022-update.html"&gt;5&lt;/a&gt;   &lt;a href="https://ralsina.me/weblog/posts/pet-server-feb-2023-update.html"&gt;6&lt;/a&gt;  post­s.&lt;/p&gt; 
 &lt;h2&gt;Hardware&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Got some SS­Ds for cheap, so mi­grat­ed it from HDDs to SDDs. Thanks to btrf­s, I could even do that with­out turn­ing the thing of­f.&lt;/li&gt;  &lt;li&gt;Did some ex­per­i­ments to mea­sure pow­er us­age. It's around 8W when it goes full throt­tle with heavy disk us­age.&lt;/li&gt;  &lt;li&gt;Work­ing on im­ple­ment­ing a UPS for the whole sys­tem.&lt;/li&gt;  &lt;/ul&gt;

&lt;h2&gt;Software&lt;/h2&gt;

&lt;p&gt;Since the last up­date I have added a few new ser­vices:&lt;/p&gt; 
 &lt;h3&gt;Filebrowser&lt;/h3&gt;

&lt;p&gt;I added a we­b-based file­brows­er. It's ...  &lt;a href="https://github.com/filebrowser/filebrowser/releases"&gt;File­brows­er&lt;/a&gt;&lt;/p&gt; 
 &lt;p&gt;Why? Be­cause some­times I need to man­age some files. No big deal, al­most nev­er use it.&lt;/p&gt; 
 &lt;h3&gt;Ebook Server&lt;/h3&gt;

&lt;p&gt;It's  &lt;a href="https://www.kavitareader.com/"&gt;Kavi­ta&lt;/a&gt;  and it's pret­ty good, if a lit­tle too ba­sic.
I use it to man­age an epub col­lec­tion, and it work­s. Writ­ing scripts to au­to­mat­i­cal­ly tag
and add meta­da­ta to ran­dom crap­py ebooks was fun.&lt;/p&gt; 
 &lt;h3&gt;Snips&lt;/h3&gt;

&lt;p&gt;A snip­pet/­paste­bin thing called  &lt;a href="https://ralsina.me/weblog/posts/snips.sh"&gt;snip­s.sh&lt;/a&gt;  and it's re­al­ly nice. I added a  &lt;a href="https://snips.ralsina.me/f/oDNnPlMkYj"&gt;small shell script&lt;/a&gt;  so I can just pipe things to it. I am mak­ing the HTTPS pub­lic at snip­s.ralsi­na.me and keep­ing the SSH pri­vate in my VPN thank you very much.&lt;/p&gt; 
 &lt;p&gt;Had some trou­ble mak­ing it work on ARM be­cause of a ten­sor­flow de­pen­den­cy, but it's run­ning fine.&lt;/p&gt; 
 &lt;h3&gt;WatchTower&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://containrrr.dev/watchtower/"&gt;Watch­tow­er&lt;/a&gt;  is a tool that mon­i­tors your run­ning con­tain­ers and will dai­ly check if there are new ver­sion­s. If there are, it will up­grade them. Nice to have things up­date unat­tend­ed.&lt;/p&gt; 
 &lt;h3&gt;Conclusion&lt;/h3&gt;

&lt;p&gt;Serv­er is sta­ble&lt;/p&gt;</description><category>hardware</category><category>linux</category><category>software</category><guid>https://ralsina.me/weblog/posts/home-server-update-may-2023.html</guid><pubDate>Mon, 15 May 2023 16:02:50 GMT</pubDate></item><item><title>Columbus Was A Dope</title><link>https://ralsina.me/weblog/posts/columbus-was-a-dope.html</link><dc:creator>Roberto Alsina</dc:creator><description>&lt;div&gt;
&lt;table&gt;
&lt;tr&gt;
&lt;td valign="top"&gt;
&lt;img class="img-thumbnail" style="width:10vw;" src="https://i.gr-assets.com/images/S/compressed.photo.goodreads.com/books/1575881914l/19239416._SY475_.jpg" width="10vw" alt="Cover for Columbus Was A Dope"&gt;
&lt;/td&gt;
&lt;td valign="top"&gt;
&lt;div class="panel"&gt;
&lt;ul class="list-group"&gt;
&lt;li class="list-group-item"&gt;  &lt;b&gt;Au­thor:&lt;/b&gt;  Robert A. Hein­lein &lt;/li&gt;&lt;li class="list-group-item"&gt;  &lt;b&gt;Rat­ing:&lt;/b&gt; 
★
★
★
★ &lt;/li&gt;&lt;li class="list-group-item"&gt;  &lt;a href="https://www.goodreads.com/review/show/5544334007?utm_medium=api&amp;amp;utm_source=rss"&gt;See in goodreads&lt;/a&gt;  &lt;/li&gt;&lt;/ul&gt;
&lt;h3&gt;Review:&lt;/h3&gt;
&lt;p&gt;Not go­ing to make this long, since it's just a short sto­ry, but it's a nice com­bi­na­tion of re­al­ly out­dat­ed writ­ing and an ear for a kind of di­a­log that no hu­man ev­er used :-)&lt;/p&gt;  &lt;/div&gt;
&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;</description><category>books</category><category>goodreads</category><guid>https://ralsina.me/weblog/posts/columbus-was-a-dope.html</guid><pubDate>Thu, 11 May 2023 16:47:47 GMT</pubDate></item><item><title>Brute Force</title><link>https://ralsina.me/weblog/posts/brute-force.html</link><dc:creator>Roberto Alsina</dc:creator><description>&lt;div&gt;
&lt;table&gt;
&lt;tr&gt;
&lt;td valign="top"&gt;
&lt;img class="img-thumbnail" style="width:10vw;" src="https://i.gr-assets.com/images/S/compressed.photo.goodreads.com/books/1679509685l/123416781._SY475_.jpg" width="10vw" alt="Cover for Brute Force"&gt;
&lt;/td&gt;
&lt;td valign="top"&gt;
&lt;div class="panel"&gt;
&lt;ul class="list-group"&gt;
&lt;li class="list-group-item"&gt;  &lt;b&gt;Au­thor:&lt;/b&gt;  Scott  Mey­er &lt;/li&gt;&lt;li class="list-group-item"&gt;  &lt;b&gt;Rat­ing:&lt;/b&gt; 
★
★
★ &lt;/li&gt;&lt;li class="list-group-item"&gt;  &lt;a href="https://www.goodreads.com/review/show/5509328290?utm_medium=api&amp;amp;utm_source=rss"&gt;See in goodreads&lt;/a&gt;  &lt;/li&gt;&lt;/ul&gt;
&lt;h3&gt;Review:&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Not bad, ac­tu­al­ly!&lt;/strong&gt;&lt;br&gt;&lt;br&gt;Bet­ter than the lat­est books I read from the au­thor. In­ter­est­ing se­tup, di­a­log on­ly marginal­ly cringe.&lt;br&gt;&lt;br&gt;Ful­fill­ing the word quo­ta.&lt;/p&gt;  &lt;/div&gt;
&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;</description><category>books</category><category>goodreads</category><guid>https://ralsina.me/weblog/posts/brute-force.html</guid><pubDate>Mon, 08 May 2023 00:19:05 GMT</pubDate></item><item><title>Color Coordination Using Base16</title><link>https://ralsina.me/weblog/posts/color-coordination-using-base16/</link><dc:creator>Roberto Alsina</dc:creator><description>&lt;h2&gt;The Problem&lt;/h2&gt;

&lt;p&gt;A few days ago  &lt;a href="http://ralsina.me/weblog/posts/desktop-setup/"&gt;I wrote&lt;/a&gt;  about how I moved from a al­l-in­clu­sive en­vi­ron­ment (KDE Plas­ma) to  &lt;a href="https://qtile.org"&gt;Qtile&lt;/a&gt;, a tiling win­dow man­ager, and all the things I did to have the ameni­ties I missed from Plas­ma.&lt;/p&gt; 
 &lt;p&gt;One thing there that may need fur­ther ex­pla­na­tion is the sec­tion about  &lt;a href="http://ralsina.me/weblog/posts/desktop-setup/#color-schemes"&gt;col­or schemes&lt;/a&gt;  be­cause the con­cepts them­selves are fair­ly un­usu­al.&lt;/p&gt; 
 &lt;p&gt;So, let's dig in­to it a bit more.&lt;/p&gt; 
 &lt;p&gt;This is how a few apps look by de­fault if you don't con­fig­ure things:&lt;/p&gt; 
 &lt;p&gt;&lt;img src="https://ralsina.me/weblog/posts/color-coordination-using-base16/unconfigured.png" alt="unconfigured apps"&gt;&lt;/p&gt; 
 &lt;p&gt;One is a Qt ap­p, and the oth­er is a GTK app and they look ... bad?&lt;/p&gt; 
 &lt;p&gt;I mean, I don't re­al­ly care all that much, but not on­ly are the wid­get styles to­tal­ly dif­fer­en­t, but the  &lt;em&gt;col­ors&lt;/em&gt;  are all over the place.&lt;/p&gt; 
 &lt;p&gt;And the same hap­pens to Qtile's bar, and my ter­mi­nal (alacrit­ty) and my tmux­er (zel­li­j) and my web brows­er and so on.&lt;/p&gt; 
 &lt;p&gt;Ev­ery­thing  &lt;em&gt;is a dif­fer­ent col­or&lt;/em&gt;.&lt;/p&gt; 
 &lt;p&gt;In Plas­ma, a comon col­or scheme is en­forced on most apps (not in chrome, though) and it makes things much nicer.&lt;/p&gt; 
 &lt;p&gt;So, what's the equiv­a­lent in the Win­dow Man­ag­er world? There is­n't one, re­al­ly, but there are bits and pieces you can make in­to ... 80% of it.&lt;/p&gt; 
 &lt;p&gt;Here's what I did.&lt;/p&gt; 
 &lt;h2&gt;My Solution&lt;/h2&gt;

&lt;p&gt;First, you need a source of con­sis­tent col­or schemes. If you just re­al­ly, re­al­ly, re­al­ly like a sin­gle one, then they may have a site full of con­fig­u­ra­tions for dif­fer­ent apps to force them to fol­low it.&lt;/p&gt; 
 &lt;p&gt;For ex­am­ple:  &lt;a href="https://draculatheme.com/contribute"&gt;Drac­u­la&lt;/a&gt;  or  &lt;a href="https://www.nordtheme.com/"&gt;Nord&lt;/a&gt;&lt;/p&gt; 
 &lt;p&gt;But I want­ed to be able to try a bunch and see which one I liked.&lt;/p&gt; 
 &lt;p&gt;For this, there is noth­ing like  &lt;a href="https://github.com/chriskempson/base16"&gt;base16&lt;/a&gt;&lt;/p&gt; 
 &lt;p&gt;Base16 de­fines a stan­dard. You will have 16 col­ors. Those col­ors mean things.  &lt;/p&gt; 
 &lt;ul&gt;
&lt;li&gt;Col­or 0 is the back­ground.&lt;/li&gt;  &lt;li&gt;Col­or 1 is the fore­ground.&lt;/li&gt;  &lt;/ul&gt;

&lt;p&gt;And so on.&lt;/p&gt; 
 &lt;p&gt;Then it de­fines some oth­er things, like "If you are do­ing a 'dark' scheme, col­ors go from dark­er to lighter" and so on.&lt;/p&gt; 
 &lt;p&gt;Then they pro­vide a col­lec­tion of themes, which are each 16 col­ors fol­low­ing those rules.&lt;/p&gt; 
 &lt;p&gt;For ex­am­ple, this is the  &lt;a href="https://github.com/vviikk/base16-brogrammer-scheme/blob/master/brogrammer.yaml"&gt;Bro­gram­mer Theme&lt;/a&gt;:&lt;/p&gt; 
 &lt;pre&gt;&lt;code class="language-yaml"&gt;scheme: "Brogrammer"
author: "Vik Ramanujam (http://github.com/piggyslasher)"
base00: "1f1f1f"
base01: "f81118"
base02: "2dc55e"
base03: "ecba0f"
base04: "2a84d2"
base05: "4e5ab7"
base06: "1081d6"
base07: "d6dbe5"
base08: "d6dbe5"
base09: "de352e"
base0A: "1dd361"
base0B: "f3bd09"
base0C: "1081d6"
base0D: "5350b9"
base0E: "0f7ddb"
base0F: "ffffff"
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Now, sup­pose you want an app to fol­low the "base 16 stan­dard" to see how it looks in Bro­gram­mer style.&lt;/p&gt; 
 &lt;p&gt;All you need to do is take that ap­p's con­fig file and put the right col­or where it needs it.&lt;/p&gt; 
 &lt;p&gt;So, for ex­am­ple, for the Alacrit­ty ter­mi­nal:&lt;/p&gt; 
 &lt;pre&gt;&lt;code class="language-yaml"&gt;colors:
  # Default colors
  primary:
    background: '0x1b1918'
    foreground: '0xa8a19f'

  # Colors the cursor will use if `custom_cursor_colors` is true
  cursor:
    text: '0x1b1918'
    cursor: '0xa8a19f'

  # Normal colors
  normal:
    black:   '0x1b1918'
    red:     '0xf22c40'
    green:   '0x7b9726'
    yellow:  '0xc38418'
    blue:    '0x407ee7'
    magenta: '0x6666ea'
    cyan:    '0x3d97b8'
    white:   '0xa8a19f'
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Of course edit­ing all the con­fig files ev­ery time you want to change your col­or scheme is a pain.&lt;/p&gt; 
 &lt;p&gt;So the base16 project al­so col­lects  &lt;strong&gt;tem­plates&lt;/strong&gt;. Those are files that when com­bined with a theme gen­er­ate the con­fig­u­ra­tion file for an ap­pli­ca­tion.&lt;/p&gt; 
 &lt;p&gt;This is the  &lt;a href="https://github.com/aarowill/base16-alacritty"&gt;Alacrit­ty tem­plate&lt;/a&gt;:&lt;/p&gt; 
 &lt;pre&gt;&lt;code class="language-yaml"&gt;# Base16 {{scheme-name}} - alacritty color config
# {{scheme-author}}
colors:
  # Default colors
  primary:
    background: '0x{{base00-hex}}'
    foreground: '0x{{base05-hex}}'

  # Colors the cursor will use if `custom_cursor_colors` is true
  cursor:
    text: '0x{{base00-hex}}'
    cursor: '0x{{base05-hex}}'

  # Normal colors
  normal:
    black:   '0x{{base00-hex}}'
    red:     '0x{{base08-hex}}'
    green:   '0x{{base0B-hex}}'
    yellow:  '0x{{base0A-hex}}'
    blue:    '0x{{base0D-hex}}'
    magenta: '0x{{base0E-hex}}'
    cyan:    '0x{{base0C-hex}}'
    white:   '0x{{base05-hex}}'
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;See those bits like &lt;code&gt;{{base05-hex}}&lt;/code&gt;? That one gets replaced with your theme's color 5.&lt;/p&gt;

&lt;p&gt;But again, us­ing the tem­plate for each app you want to theme is bor­ing.&lt;/p&gt; 
 &lt;p&gt;So you need a tool to do that. The one I use is called  &lt;a href="https://github.com/Misterio77/flavours"&gt;flavours&lt;/a&gt;&lt;/p&gt; 
 &lt;p&gt;What flavours does is:&lt;/p&gt; 
 &lt;ul&gt;
&lt;li&gt;Get all the base16 col­or themes&lt;/li&gt;  &lt;li&gt;Get all the base16 app con­fig tem­plates&lt;/li&gt;  &lt;li&gt;Fol­low­ing a con­fig­u­ra­tion file, gen­er­ate all the con­fig files you need.&lt;/li&gt;  &lt;li&gt;Op­tion­al­ly: run com­mands so the apps no­tice their con­figs have changed.&lt;/li&gt;  &lt;/ul&gt;

&lt;p&gt;For ex­am­ple, this is my con­fig­u­ra­tion for Alacrit­ty:&lt;/p&gt; 
 &lt;pre&gt;&lt;code class="language-toml"&gt;[[items]]
file = "~/.config/alacritty/alacritty.yml"
template = "alacritty"
subtemplate = "default-256"
rewrite = false
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;It says:&lt;/p&gt; 
 &lt;ul&gt;
&lt;li&gt;Generate &lt;code&gt;~/.config/alacritty/alacritty.yml&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Use the &lt;code&gt;alacritty&lt;/code&gt; template &lt;/li&gt;
&lt;li&gt;From that template use the version called &lt;code&gt;default-256&lt;/code&gt; (don't worry)&lt;/li&gt;
&lt;li&gt;DO NOT RECRE­ATE THE WHOLE FILE&lt;/li&gt;  &lt;/ul&gt;

&lt;p&gt;That last bit is im­por­tan­t. That file does­n't just have the the­me, it has a lot of oth­er im­por­tant stuff, so I don't want it to just have the theme in it.&lt;/p&gt; 
 &lt;p&gt;So, I had to ed­it it once and put these lines be­fore and af­ter where the theme goes:&lt;/p&gt; 
 &lt;pre&gt;&lt;code class="language-yaml"&gt;# Start flavours
... theme goes here
# End flavours
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;So when I use &lt;code&gt;flavours&lt;/code&gt; to apply a theme it will only replace that bit and leave the rest.&lt;/p&gt;

&lt;p&gt;Since alacritty notices when the config has changed, I don't need a &lt;code&gt;hook&lt;/code&gt; entry. In other cases you can use it. Here's mine for qtile, to let it know it should reread its config:&lt;/p&gt;

&lt;pre&gt;&lt;code class="language-yaml"&gt;hook = "killall -USR1 qtile"
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;So, I configured this for a bunch of things, and at this point, I can just run something like &lt;code&gt;flavours apply atelier-forest&lt;/code&gt; and ...&lt;/p&gt;

&lt;p&gt;&lt;img src="https://ralsina.me/weblog/posts/color-coordination-using-base16/configured.png" alt="color coordinated apps"&gt;&lt;/p&gt; 
 &lt;p&gt;Yeah, they are not per­fec­t. I still need to tweak some stuff, but it's get­ting there.&lt;/p&gt; 
 &lt;p&gt;Cur­rent­ly my flavours con­fig­u­ra­tion co­or­di­nates the­se:&lt;/p&gt; 
 &lt;ul&gt;
&lt;li&gt;Alacrit­ty&lt;/li&gt;  &lt;li&gt;Qtile&lt;/li&gt;  &lt;li&gt;Zel­lij&lt;/li&gt;  &lt;li&gt;Rofi&lt;/li&gt;  &lt;li&gt;Qt&lt;/li&gt;  &lt;li&gt;Gtk (us­ing Flat­col­or)&lt;/li&gt;  &lt;li&gt;VS Code&lt;/li&gt;  &lt;li&gt;Qute­Brows­er&lt;/li&gt;  &lt;/ul&gt;

&lt;p&gt;That's rough­ly ev­ery app I use (yes, I am mi­grat­ing from Chrome to Qute­Brows­er for no good rea­son)&lt;/p&gt; 
 &lt;p&gt;And be­cause I am not ok, I wrote a cou­ple of tiny things to make things eas­i­er for me.&lt;/p&gt; 
 &lt;p&gt;A rofi-based theme pick­er called  &lt;a href="https://github.com/ralsina/rofi-base16"&gt;rofi-base16&lt;/a&gt;:&lt;/p&gt; 
 &lt;p&gt;&lt;img src="https://ralsina.me/weblog/posts/color-coordination-using-base16/rofi-base16.png" alt="rofi-base16 in action"&gt;&lt;/p&gt; 
 &lt;p&gt;And a  &lt;a href="https://github.com/ralsina/base16-zellij"&gt;base 16 tem­plate for Zel­li­j.&lt;/a&gt;&lt;/p&gt; 
 &lt;p&gt;I al­so wrote an ug­ly script to set VS Code's theme but it's shame­ful,  &lt;a href="https://gist.github.com/ralsina/b7f7bce7f929da7f7eee64510404cc6d#start-of-content"&gt;so will not be shown here&lt;/a&gt;.&lt;/p&gt; 
 &lt;p&gt;Hope­ful­ly this ex­plains some­thing to some­one. If it does­n't, well, it will help me when I in­evitably for­get how this works :-)&lt;/p&gt;</description><category>base16</category><category>color</category><category>desktop</category><category>linux</category><category>qtile</category><category>tiling</category><category>windowmanager</category><guid>https://ralsina.me/weblog/posts/color-coordination-using-base16/</guid><pubDate>Thu, 04 May 2023 15:18:17 GMT</pubDate></item><item><title>Desktop setup</title><link>https://ralsina.me/weblog/posts/desktop-setup/</link><dc:creator>Roberto Alsina</dc:creator><description>&lt;h2&gt;Introduction&lt;/h2&gt;

&lt;p&gt;I am a long-term KDE us­er. How long ter­m? Well, since 1997 or so with in­ter­mit­ten­cies. A cou­ple of weeks ago, I switched my per­son­al desk­tops to  &lt;a href="http://www.qtile.org/"&gt;QTile&lt;/a&gt;.&lt;/p&gt; 
 &lt;p&gt;Manda­to­ry screen­shot:&lt;/p&gt; 
 &lt;p&gt;&lt;img src="https://pbs.twimg.com/media/Fu-nhW2WcAEKsP6?format=jpg&amp;amp;name=large" alt="A desktop"&gt;&lt;/p&gt; 
 &lt;h2&gt;Why?&lt;/h2&gt;

&lt;p&gt;Well, I like to switch things around ev­ery few years. I have used Uni­ty, and MATE, and even LxQt at some point, and I have no­ticed over the years that I don't tend to over­lap win­dows, so tiling win­dow man­agers are the nat­u­ral pro­gres­sion of my us­age.&lt;/p&gt; 
 &lt;p&gt;For a few years I have used  &lt;a href="https://github.com/esjeon/krohnkite"&gt;Krohnkite&lt;/a&gt;  an  &lt;strong&gt;awe­some&lt;/strong&gt;  KWin ex­ten­sion that has re­al­ly been a plea­sure to use and proved me that a tiling WM, with a few rea­son­able key­board short­cut­s, was a work­able so­lu­tion.&lt;/p&gt; 
 &lt;p&gt;Late­ly I have been build­ing a  &lt;a href="https://ralsina.me/tr/es/weblog/posts/so-i-built-a-laptop.html"&gt;home­brew lap­top&lt;/a&gt;  which has fair­ly lim­it­ed hard­ware, but I want to use for re­al. So I al­so want­ed to ex­plore ways to low­er re­source us­age.&lt;/p&gt; 
 &lt;p&gt;Oth­er re­lat­ed con­cern­s:  &lt;/p&gt; 
 &lt;ul&gt;
&lt;li&gt;I have very dy­nam­ic mon­i­tor con­fig­u­ra­tions. My com­put­ers gain and lose mon­i­tor all the time, and this needs to be han­dled au­to­mat­i­cal­ly.&lt;/li&gt;  &lt;li&gt;I have mul­ti­ple com­put­ers (work, home, home­brew) and they all have very dif­fer­ent set­ups and con­fig­u­ra­tions, but I want them all to work the same.&lt;/li&gt;  &lt;li&gt;I use  &lt;a href="https://www.chezmoi.io/"&gt;chez­moi&lt;/a&gt;  to man­age my con­fig­u­ra­tions so I want con­fig to be sim­ple text files I can store with a min­i­mum of tem­plat­ing.&lt;/li&gt;  &lt;li&gt;I want com­fort. I want disk au­to­moun­ing, vol­ume keys, blue­tooth menus, wifi con­fig­u­ra­tion, a clock, bat­tery in­for­ma­tion, no­ti­fi­ca­tions and so on.&lt;/li&gt;  &lt;/ul&gt;

&lt;p&gt;But why QTile specif­i­cal­ly:&lt;/p&gt; 
 &lt;ul&gt;
&lt;li&gt;It's tiling but with a nice at­ti­tude. The whole "hard­core" thing in both tiling WM users and projects is a bit tire­some.&lt;/li&gt;  &lt;li&gt;It's writ­ten, con­fig­urable and ex­tend­able in Python, which I like.&lt;/li&gt;  &lt;li&gt;It works rea­son­ably out of the box. No, a black screen where you can do noth­ing with­out look­ing up short­cuts in your phone at first is too much.&lt;/li&gt;  &lt;li&gt;It's "dy­nam­ic". This is one of those minute sub­cat­e­gories you can see in WM fan­dom, but ba­si­cal­ly: win­dows get placed more or less cor­rect­ly by de­fault, while oth­er WMs will make you ex­plic­it­ly han­dle each win­dow as it ap­pears, which sounds sort of like tor­ture to me.&lt;/li&gt;  &lt;/ul&gt;

&lt;h2&gt;So what?&lt;/h2&gt;

&lt;p&gt;So I will now de­scribe painstak­ing­ly ev­ery de­ci­sion I made as of to­day, and de­scribe the im­por­tant con­fig­u­ra­tion, and bore any­one who tries to read this to tears, that's what.&lt;/p&gt; 
 &lt;p&gt;In an at­tempt to make this a bit less an­noy­ing I will set­up a prob­lem, then de­scribe the so­lu­tion I found.&lt;/p&gt; 
 &lt;h2&gt;Uniform UI for utilities&lt;/h2&gt;

&lt;h3&gt;The Problem&lt;/h3&gt;

&lt;p&gt;There are a num­ber of ways you in­ter­act with your "desk­top". You may want to lo­gout. Or start an ap­p. Or run a com­mand. Or con­fig­ure a blue­tooth speak­er. Or see the clip­board his­to­ry. Or con­fig­ure wifi.&lt;/p&gt; 
 &lt;p&gt;In the desk­top world, this all comes in­clud­ed in the pack­age deal. In the Win­dow Man­ag­er uni­verse, you have to roll your own, and most set­ups end with a dis­parate col­lec­tion of util­i­ties, like blue­man and net­work-­man­ag­er and clip­it and so on plus what­ev­er the WM it­self pro­vides.&lt;/p&gt; 
 &lt;p&gt;Well, but what if they  &lt;em&gt;could&lt;/em&gt;  be the same thing?&lt;/p&gt; 
 &lt;p&gt;What if in ad­di­tion they could all share a love­ly, cus­tom­iz­a­ble UI?&lt;/p&gt; 
 &lt;h3&gt;The Solution&lt;/h3&gt;

&lt;p&gt;En­ter  &lt;a href="https://github.com/davatorium/rofi"&gt;rofi&lt;/a&gt;&lt;/p&gt; 
 &lt;p&gt;the most com­mon us­age for rofi is as an app launcher, and it's great at that, but it's just scratch­ing the sur­face:&lt;/p&gt; 
 &lt;p&gt;&lt;img src="https://ralsina.me/weblog/posts/desktop-setup/rofi-launcher.png" alt="rofi as launcher"&gt;&lt;/p&gt; 
 &lt;p&gt;Be­cause of its flex­i­ble ar­chi­tec­ture, it can be used to con­fig­ure your net­work us­ing  &lt;a href="https://github.com/firecat53/networkmanager-dmenu"&gt;net­work­man­ager-d­menu&lt;/a&gt;:&lt;/p&gt; 
 &lt;p&gt;&lt;img src="https://ralsina.me/weblog/posts/desktop-setup/rofi-network.png" alt="rofi as network manager"&gt;&lt;/p&gt; 
 &lt;p&gt;As your pow­er/l­o­gout menu via  &lt;a href="https://github.com/jluttine/rofi-power-menu"&gt;rofi-pow­er-­menu&lt;/a&gt;&lt;/p&gt; 
 &lt;p&gt;&lt;img src="https://ralsina.me/weblog/posts/desktop-setup/rofi-power.png" alt="rofi as power menu"&gt;&lt;/p&gt; 
 &lt;p&gt;And much more. I al­so use it as a  &lt;a href="https://ralsina.me/weblog/posts/desktop-setup/#chrome-hates-wms"&gt;front­ed to a pas­word man­ag­er&lt;/a&gt;, a fron­tend to ssh, a sim­ple To­do list, an emo­ji pick­er, as a fan­cy win­dow switcher, and as a clip­board his­to­ry man­ag­er us­ing  &lt;a href="https://aur.archlinux.org/packages/rofi-greenclip"&gt;rofi-­green­clip&lt;/a&gt;&lt;/p&gt; 
 &lt;p&gt;&lt;img src="https://ralsina.me/weblog/posts/desktop-setup/rofi-clipboard.png" alt="rofi as clipboard manager"&gt;&lt;/p&gt; 
 &lt;p&gt;Rofi starts so fast that it's of­ten im­posi­ble to no­tice the dif­fer­ence be­tween hav­ing a tool al­ways run­ning and with an icon in the systray and just hav­ing a but­ton that launch­es rofi.&lt;/p&gt; 
 &lt;p&gt;So, how do I use it in my &lt;code&gt;~/.config/qtile/config.py&lt;/code&gt;? &lt;/p&gt;

&lt;p&gt;Bound to some key short­cut­s:&lt;/p&gt; 
 &lt;pre&gt;&lt;code class="language-python"&gt;keys = [
    # Launch things
    Key([mod], 'p', lazy.spawn('rofi -show drun'), desc="Run app"),
    Key([mod, "shift"], 'p', lazy.spawn('rofi -show run'), desc="Run Command"),
    Key([mod], 's', lazy.spawn('rofi -show ssh'), desc="Ssh"),
    Key([mod], 'e', lazy.spawn('rofi -show emoji'), desc="Emoji"),
    Key([mod], 'v', lazy.spawn('rofi -modi "clipboard:greenclip print" -show'), desc="Clipboard")
    Key([mod, alt], "Tab", lazy.spawn('rofi -show window'), desc="Move focus to next window"),
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;I al­so added a  &lt;a href="https://docs.qtile.org/en/latest/manual/ref/widgets.html#launchbar"&gt;Launch­Bar wid­get&lt;/a&gt;  to my QTile bar where it re­places some­thing like  &lt;a href="https://github.com/CristianHenzel/ClipIt"&gt;clip­it&lt;/a&gt;, the blue­man ap­plet, a pass­word man­ag­er and a to­do ap­p:&lt;/p&gt; 
 &lt;pre&gt;&lt;code class="language-python"&gt;    widget.LaunchBar(padding=0, text_only=True, font="Font Awesome 6 Free", fontsize=16, foreground="2e3440", progs=[
        ("", "rofi-bluetooth", "Bluetooth"),
        ("", "rofi -modi 'clipboard:greenclip print' -show", "Clipboard History"),
        ("", "rofi-pass", "Passwords"),
        ("", "rofi-todo -f /home/ralsina/todo.txt", "Todo"),
        ("", "flameshot gui", "Screenshot"),
    ]),
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;The first field that prob­a­bly looks like a square in there are uni­code char­ac­ters dis­played on the desk­top us­ing font awe­some:&lt;/p&gt; 
 &lt;p&gt;&lt;img src="https://ralsina.me/weblog/posts/desktop-setup/rofi-launchbar.png" alt="launchbar that uss rofi"&gt;&lt;/p&gt; 
 &lt;p&gt;Al­so for mouse in­ter­ac­tions with the  &lt;a href="https://docs.qtile.org/en/latest/manual/ref/widgets.html#wlan"&gt;Wlan wid­get:&lt;/a&gt;&lt;/p&gt; 
 &lt;pre&gt;&lt;code class="language-python"&gt;widget.Wlan(format=" {percent:2.0%}",width=54, mouse_callbacks={'Button1': lazy.spawn('networkmanager_dmenu')}),
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;I even use it to show me the short­cut list in case I for­got any!&lt;/p&gt; 
 &lt;p&gt;&lt;img src="https://pbs.twimg.com/media/FvEKyj6XoAAaZKs?format=png&amp;amp;name=900x900" alt="rofi showing shortcuts"&gt;&lt;/p&gt; 
 &lt;p&gt;I could write some mi­nor code and use it for oth­er things too. Rofi is a mag­nif­i­cent ex­am­ple of a tool with a nice API that you can lever­age to make it do many dif­fer­ent things.&lt;/p&gt; 
 &lt;h2&gt;Display Configuration&lt;/h2&gt;

&lt;h3&gt;The Problem&lt;/h3&gt;

&lt;p&gt;My dis­play con­fig­u­ra­tions are many. I use note­book­s, so they all have one "de­fault" con­fig­u­ra­tion where they use their own screen.&lt;/p&gt; 
 &lt;p&gt;In one case, it's all it can use.&lt;/p&gt; 
 &lt;p&gt;In an­oth­er, it's per­ma­nent­ly docked to a mon­i­tor  &lt;em&gt;above&lt;/em&gt;  its screen, but some­times I close the note­book and the on­ly dis­play is the mon­i­tor.&lt;/p&gt; 
 &lt;p&gt;In an­oth­er it's usu­al­ly docked to a mon­i­tor  &lt;em&gt;be­sides&lt;/em&gt;  its screen, but it's shared with my work com­put­er, so it los­es and re­cov­ers that mon­i­tor dozens of times via a HD­MI switch through the day as I switch tasks.&lt;/p&gt; 
 &lt;p&gt;While QTile han­dles grace­ful­ly the ad­di­tion and re­moval of dis­plays in gen­er­al, there are some is­sues.&lt;/p&gt; 
 &lt;ul&gt;
&lt;li&gt;Us­ing the HD­MI switch did­n't make the mon­i­tor "go away" which caused some is­sues with  &lt;a href="https://github.com/debauchee/barrier"&gt;bar­ri­er&lt;/a&gt;&lt;/li&gt;  &lt;li&gt;The con­fig file de­scribes the "bars" for each screen. So I  &lt;em&gt;need&lt;/em&gt;  to put the main bar that has the systray on a screen that is al­ways on. But the screens are one per-out­put and in a ma­chine-de­pen­dent or­der, and things got com­pli­cat­ed re­al­ly quick. Here Plas­ma does the right thing ef­fort­less­ly.&lt;/li&gt;  &lt;/ul&gt;

&lt;h3&gt;The Solution&lt;/h3&gt;

&lt;p&gt;En­ter  &lt;a href="https://github.com/phillipberndt/autorandr"&gt;au­toran­dr&lt;/a&gt;  an awe­some tool that will re­act to changes in your sit­u­a­tion and do the right thing.&lt;/p&gt; 
 &lt;p&gt;How?&lt;/p&gt; 
 &lt;ul&gt;
&lt;li&gt;Set your machine as you normally use it say, with two monitors using &lt;a href="https://christian.amsuess.com/tools/arandr/"&gt;arandr&lt;/a&gt; or something. Then &lt;code&gt;autorandr -s two-monitors&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Set your machine in its alternate config, say, with the lid closed. Then &lt;code&gt;autorandr -s closed-lid&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Put &lt;code&gt;autorandr -c&lt;/code&gt; in your &lt;code&gt;~.xprofile&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is enough for most things, ex­cept the bar sit­u­a­tion in QTile.&lt;/p&gt; 
 &lt;p&gt;For that, I wrote two things. First a shell script that goes in &lt;code&gt;~/.config/autorandr/postswitch&lt;/code&gt; This is a "hook" that gets executed after the display configuration changes. Here's mine:&lt;/p&gt;

&lt;pre&gt;&lt;code class="language-sh"&gt;#!/bin/bash -x

config="$AUTORANDR_CURRENT_PROFILE"
notify-send "Switched to config $AUTORANDR_CURRENT_PROFILE"
killall -USR1 qtile  # Make qtile reread configuration
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;And why do I want qtile to reread its con­fig­u­ra­tion? Be­cause I want the systray to be on the "last" screen, which is al­ways larg­er :-)&lt;/p&gt; 
 &lt;p&gt;Here's the relevant fragment from &lt;code&gt;~/.config/qtile/conf.py&lt;/code&gt;&lt;/p&gt;

&lt;pre&gt;&lt;code class="language-python"&gt;screen_count = len(subprocess.check_output(shlex.split("xrandr --listmonitors")).splitlines()) -1

screens = [
    Screen(
        bottom=bar.Bar(
            [ ... # List of widgets for this bar
            # Add systray in the LAST screen, otherwise invisible widget
            widget.Systray(padding=10) if i==screen_count-1 else widget.Sep(linewidth=0),),
    ) for i in range(screen_count)]  
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;This so­lu­tion re­quires a min­i­mum of set­up for my stan­dards (just con­fig­ure your screens once and a bit of code) and a max­i­mum of ben­e­fit:&lt;/p&gt; 
 &lt;ul&gt;
&lt;li&gt;The same con­fig works on all my ma­chi­nes, ex­cept for au­toran­dr pro­files, which have to be done on each com­put­er&lt;/li&gt;  &lt;li&gt;It works in a to­tal­ly au­to­mat­ed man­ner, I  &lt;em&gt;nev­er&lt;/em&gt;  need to move screens around&lt;/li&gt;  &lt;li&gt;It will work on any fu­ture com­put­ers thank to chez­moi&lt;/li&gt;  &lt;/ul&gt;

&lt;h2&gt;Chrome hates WMs&lt;/h2&gt;

&lt;h3&gt;The Problem&lt;/h3&gt;

&lt;p&gt;I have seen it a few times in the past. I change WMs or desk­tops and sud­den­ly chrome has no idea where my pass­words wen­t. They are there in pass­word­s.­google.­com but chrome nev­er aut­ofill­s, and they are not list­ed in my lo­cal pro­file.&lt;/p&gt; 
 &lt;h3&gt;The Solution&lt;/h3&gt;

&lt;p&gt;Make sure you are running &lt;code&gt;gnome-keyring-daemon&lt;/code&gt;  and add this in &lt;code&gt;~/.config/chrome-flags.conf&lt;/code&gt;&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;--password-store=gnome
&lt;/code&gt;&lt;/pre&gt;

&lt;h3&gt;The Other Solution&lt;/h3&gt;

&lt;p&gt;You want a pass­word store that is not chrome. I have one  &lt;strong&gt;in ad­di­tion&lt;/strong&gt;  of chrome, us­ing  &lt;a href="https://www.passwordstore.org/"&gt;pass&lt;/a&gt;&lt;/p&gt; 
 &lt;p&gt;This keeps my pass­words en­crypt­ed in Git (y­ou can use GitHub, I use my  &lt;a href="https://ralsina.me/weblog/posts/desktop-setup/git.ralsina.me"&gt;per­son­al gitea&lt;/a&gt;) and you can ac­cess them from the com­mand line, from a Chrome ex­ten­sion, or from a but­ton in my desk­top bar us­ing  &lt;a href="https://github.com/carnager/rofi-pass"&gt;rofi-­pass&lt;/a&gt;&lt;/p&gt; 
 &lt;p&gt;This en­sures I nev­er will lose ac­cess to my pass­word­s, and max­i­mizes the ways I can get to them while keep­ing things nice and se­cure.&lt;/p&gt; 
 &lt;h2&gt;Automounting Devices&lt;/h2&gt;

&lt;h3&gt;The Problem&lt;/h3&gt;

&lt;p&gt;While I know per­fect­ly well how to mount de­vices in Lin­ux, I'd rather they mount them­selves.&lt;/p&gt; 
 &lt;h3&gt;The Solution&lt;/h3&gt;

&lt;p&gt;Use  &lt;a href="https://github.com/coldfix/udiskie"&gt;ud­iskie&lt;/a&gt;  it's lightweight, nice­ly in­te­grat­ed, and it just works well by de­fault.&lt;/p&gt; 
 &lt;h2&gt;Notifications&lt;/h2&gt;

&lt;h3&gt;The Problem&lt;/h3&gt;

&lt;p&gt;I want desk­top no­ti­fi­ca­tion­s.&lt;/p&gt; 
 &lt;h3&gt;The solution&lt;/h3&gt;

&lt;p&gt;Use  &lt;a href="https://dunst-project.org/"&gt;dun­st&lt;/a&gt;  it's lightweight, nice­ly in­te­grat­ed, and it just works well by de­fault.&lt;/p&gt; 
 &lt;h2&gt;Screen Locking&lt;/h2&gt;

&lt;h3&gt;The Problem&lt;/h3&gt;

&lt;p&gt;I want my screen to lock, but don't want any­thing fan­cy.&lt;/p&gt; 
 &lt;h3&gt;The Solution&lt;/h3&gt;

&lt;p&gt;Use &lt;code&gt;xss-lock&lt;/code&gt; and &lt;a href="https://tools.suckless.org/slock/"&gt;&lt;code&gt;slock&lt;/code&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;While slock in itself works great, &lt;code&gt;xss-lock&lt;/code&gt; integrates it with &lt;code&gt;loginctl&lt;/code&gt; so it works better. Just add this to your &lt;code&gt;~/.xprofile&lt;/code&gt;:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;xss-lock slock &amp;amp;
&lt;/code&gt;&lt;/pre&gt;

&lt;h2&gt;Pretty Icons and Colors&lt;/h2&gt;

&lt;h3&gt;The Problem&lt;/h3&gt;

&lt;p&gt;I want sim­ple icons that are not very col­or­ful, so they in­te­grate with what­ev­er col­or scheme I am us­ing.  &lt;/p&gt; 
 &lt;h3&gt;The Solution&lt;/h3&gt;

&lt;p&gt;Since I am fol­low­ing more or less a  &lt;a href="https://www.nordtheme.com/"&gt;nord col­or scheme&lt;/a&gt;  I am us­ing a com­bi­na­tion of  &lt;a href="https://github.com/alvatip/Nordzy-icon"&gt;Nordzy icons&lt;/a&gt;  and just us­ing  &lt;a href="https://fontawesome.com/"&gt;font-awe­some&lt;/a&gt;&lt;/p&gt; 
 &lt;p&gt;Here are some snip­pets from the qtile con­fig. Where I am point­ing icons to a spe­cif­ic fold­er, that fold­er con­tains copies of nordzy icons con­vert­ed to PNG with trans­par­ent back­ground­s:&lt;/p&gt; 
 &lt;pre&gt;&lt;code class="language-python"&gt;bottom=bar.Bar(
    [ ...
        widget.GroupBox(highlight_method="block", foreground="2e3440", active="d08770", inactive="4c566a", this_current_screen_border="242831",this_screen_border="88c0d0"),
        widget.BatteryIcon(theme_path="/home/ralsina/.config/qtile/battery_icons", background="d8dee9", mouse_callbacks=power_callbacks),
        
        widget.Volume(fmt="",padding=0, theme_path="/home/ralsina/.config/qtile/battery_icons"),
        
        widget.LaunchBar(padding=0, text_only=True, font="Font Awesome 6 Free", fontsize=16, foreground="2e3440", progs=[
                    ("", "rofi-bluetooth", "Bluetooth"),
                    ("", "rofi -modi 'clipboard:greenclip print' -show", "Clipboard History"),
                    ("", "rofi-pass", "Passwords"),
                    ("", "rofi-todo -f /home/ralsina/todo.txt", "Todo"),
                    ("", "flameshot gui", "Screenshot"),
                ]),
    ],
    background=["d8dee9"],
    border_width=[1, 0, 0, 0],  # Draw top and bottom borders
    border_color=["b48ead","b48ead","b48ead","b48ead" ],  # Borders are magenta
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Then I configured styles and icons for gtk apps (using &lt;code&gt;lxappearance&lt;/code&gt;) and Qt apps (using &lt;code&gt;qt5ct&lt;/code&gt;) and things now look decent.&lt;/p&gt;

&lt;h2&gt;Color Schemes&lt;/h2&gt;

&lt;h3&gt;The Problem&lt;/h3&gt;

&lt;p&gt;I want a co­or­di­nat­ed col­or scheme in the dis­parate tools I am us­ing.&lt;/p&gt; 
 &lt;h3&gt;The Solution&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Up­date:&lt;/strong&gt;  I wrote a more tu­to­ri­al-­like thing about this called  &lt;a href="http://ralsina.me/weblog/posts/color-coordination-using-base16/"&gt;Col­or Co­or­di­na­tion Us­ing Base16&lt;/a&gt;&lt;/p&gt; 
 &lt;p&gt;En­ter  &lt;a href="https://github.com/Misterio77/flavours"&gt;flavours&lt;/a&gt;, a tool to gen­er­ate con­fig­u­ra­tions for dif­fer­ent things out of sim­ple tem­plates and pre­de­fined col­or schemes.&lt;/p&gt; 
 &lt;p&gt;It al­ready sup­port­ed my ter­mi­nal (alacrit­ty) and rofi, just need­ed to cre­ate tem­plates for my qtile con­fig:&lt;/p&gt; 
 &lt;pre&gt;&lt;code class="language-python"&gt;colors = {
    "background": "#{{base00-hex}}",
    "background-lighter": "#{{base01-hex}}",
    "selection-background": "#{{base02-hex}}",
    "comments": "#{{base03-hex}}",
    "foreground-dark": "#{{base04-hex}}",
    "foreground": "#{{base05-hex}}",
    "foreground-light": "#{{base06-hex}}",
    "background-light": "#{{base07-hex}}",
    "variables": "#{{base08-hex}}",
    "integers": "#{{base09-hex}}",
    "bold": "#{{base0A-hex}}",
    "strings": "#{{base0B-hex}}",
    "quotes": "#{{base0C-hex}}",
    "headings": "#{{base0D-hex}}",
    "italic": "#{{base0E-hex}}",
    "tags": "#{{base0F-hex}}",
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;And my tmux­er (zel­li­j):&lt;/p&gt; 
 &lt;pre&gt;&lt;code class="language-js"&gt;themes {
    default {
        fg "#{{base05-hex}}"
        bg "#{{base00-hex}}"
        black "#{{base00-hex}}"
        red "#{{base08-hex}}"
        green "#{{base0B-hex}}"
        yellow "#{{base0A-hex}}"
        blue "#{{base0D-hex}}"
        magenta "#{{base0E-hex}}"
        cyan "#{{base0C-hex}}"
        white "#{{base05-hex}}"
        orange "#{{base09-hex}}"
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;And put the right in­can­ta­tions in the flavours con­fig (par­tial):&lt;/p&gt; 
 &lt;pre&gt;&lt;code class="language-toml"&gt;[[items]]
file = "~/.config/qtile/colors.py"
template = "ralsina"
subtemplate = "qtile"
rewrite = true
hook = "killall -USR1 qtile"

[[items]]
file = "~/.config/zellij/themes/default.kdl"
template = "ralsina"
subtemplate = "zellij"
rewrite = true
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;I will have to configure everything I want to follow my colour scheme but I can apply it to everything with a simple command like &lt;code&gt;flavours apply monokai&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;As an ex­tra, here's a script that lets you choose the col­or scheme us­ing rofi:&lt;/p&gt; 
 &lt;pre&gt;&lt;code class="language-sh"&gt;#!/bin/sh

LAST_SCHEME=$(cat ~/.local/share/flavours/lastscheme)
SELECTED=$(flavours list | sed 's/\s/\n/g' | grep -n $LAST_SCHEME | cut -d: -f1)

flavours apply $(flavours list| sed 's/\s/\n/g' | rofi -dmenu -selected-row $SELECTED)
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;&lt;img src="https://pbs.twimg.com/media/FvIOgrnWwAEA_ie?format=jpg&amp;amp;name=medium" alt="rofi choosing a color scheme"&gt;&lt;/p&gt; 
 &lt;h2&gt;Conclusion&lt;/h2&gt;

&lt;p&gt;What can I say, I like it, it's not ug­ly, has all the func­tion­al­i­ty I want­ed and us­es around 400MB of RAM be­fore I start chrome. I am quite en­joy­ing it.&lt;/p&gt; 
 &lt;p&gt;While this site (in­ten­tion­al­ly) has no com­ments, feel free to dis­cuss this in  &lt;a href="https://www.reddit.com/r/qtile/comments/134wnjf/i_moved_to_qtile_heres_a_list_of_things_i/"&gt;Red­dit&lt;/a&gt;&lt;/p&gt;</description><category>desktop</category><category>linux</category><category>qtile</category><category>tiling</category><category>windowmanager</category><guid>https://ralsina.me/weblog/posts/desktop-setup/</guid><pubDate>Mon, 01 May 2023 15:18:17 GMT</pubDate></item></channel></rss>