<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="3.10.0">Jekyll</generator><link href="https://nekidev.github.io/feed.xml" rel="self" type="application/atom+xml" /><link href="https://nekidev.github.io/" rel="alternate" type="text/html" /><updated>2026-11-05T05:32:38+00:00</updated><id>https://nekidev.github.io/feed.xml</id><title type="html">Nyeki’s Blog</title><subtitle>A bunch of posts I write as I learn to internalize things and share my thoughts. Launching my first (of many) company. Developer in Rust, Python, and JS/TS, learning C.</subtitle><entry><title type="html">Welcome to Wlroots! Part 2</title><link href="https://nekidev.github.io/2026/11/04/1-welcome-to-wlroots-part-2.html" rel="alternate" type="text/html" title="Welcome to Wlroots! Part 2" /><published>2026-11-04T00:00:00+00:00</published><updated>2026-11-04T00:00:00+00:00</updated><id>https://nekidev.github.io/2026/11/04/1-welcome-to-wlroots-part-2</id><content type="html" xml:base="https://nekidev.github.io/2026/11/04/1-welcome-to-wlroots-part-2.html"><![CDATA[<p>Part two of the Welcome to Wlroots! series. If you haven’t yet, you should go read the <a href="/2026/10/30/1-welcome-to-wlroots">part 1 of
this series</a>.</p>

<p>It has been some days since I last touched my compositor codebase, so we’ll start by checking out
how Make works to make it easy for us to be building the project. If you’re a C expert already and
you’re comfortable with Meson or other tool, feel free to use it instead. I’m using Make because
it’s the easiest for beginners (like me).</p>

<p>Also, we’ll be using the clang compiler for this project. GCC should work just fine, yet I’ve heard
clang has better error messages.</p>

<h2 id="setting-up-the-project">Setting up The Project</h2>

<p>Make files are shell scripts with rainbows and unicorns. Compiling a C project usually includes a
lot of chaining commands, which we’ll use Make to do. Make sure to have clang, make, and pkg-config
installed in your system. You’ll also need <code class="language-plaintext highlighter-rouge">wayland-server</code> and <code class="language-plaintext highlighter-rouge">wlroots-0.19</code>. Note that in Ubuntu
at least, <code class="language-plaintext highlighter-rouge">wlroots</code> is distributed as <code class="language-plaintext highlighter-rouge">wlroots-0.19</code> and not as <code class="language-plaintext highlighter-rouge">wlroots</code>.</p>

<p>To begin with, create your project directory and put these things in it:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>src/
    main.c
.gitignore
Makefile
</code></pre></div></div>

<p>In our <code class="language-plaintext highlighter-rouge">main.c</code> file, let’s put a basic <code class="language-plaintext highlighter-rouge">main()</code> to run our code.</p>

<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kt">int</span> <span class="nf">main</span><span class="p">(</span><span class="kt">void</span><span class="p">)</span> <span class="p">{</span>
    <span class="k">return</span> <span class="mi">0</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Then in our Makefile, let’s write this (indented with tabs, spaces won’t work):</p>

<div class="language-makefile highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># Variable for all the Clang flags we'll be using. It enables warnings, makes warnings fail to
# compile, enables wlroots unstable features (wlroots has lots of things marked unstable), and
# enables AddressSanitizer to help us catch memory bugs.
</span><span class="nv">CFLAGS</span> <span class="o">=</span> <span class="nt">-o</span> build/compositor <span class="nt">--std</span> c23 <span class="nt">-Wall</span> <span class="nt">-Wextra</span> <span class="nt">-Wpedantic</span> <span class="nt">-Werror</span> <span class="nt">-DWLR_USE_UNSTABLE</span> <span class="se">\</span>
	<span class="nt">-Iwayland</span> <span class="nt">-g</span> <span class="nt">-O0</span> <span class="nt">-fsanitize</span><span class="o">=</span>address <span class="nt">-fno-omit-frame-pointer</span>

<span class="c"># Our build script, we just make an out dir and compile our main.c file.
</span><span class="nl">build</span><span class="o">:</span>
	<span class="nb">mkdir</span> <span class="nt">-p</span> build
	clang src/main.c <span class="nv">$(CFLAGS)</span>

<span class="c"># This tells Make that `build` is a command and not a file we want to output.
</span><span class="nl">.PHONY</span><span class="o">:</span> <span class="nf">build</span>
</code></pre></div></div>

<p>If you run <code class="language-plaintext highlighter-rouge">make</code> or <code class="language-plaintext highlighter-rouge">make build</code> in your terminal, it should output a binary you can execute at
<code class="language-plaintext highlighter-rouge">build/compositor</code>.</p>

<p>Now that that’s done, time to start building our compositor!</p>

<h2 id="our-first-compositor">Our First Compositor</h2>

<p>Every wayland and wlroots compositor has a <code class="language-plaintext highlighter-rouge">wl_display</code> ruling it all. It handles the event loop
and incoming connections. Let’s create one!</p>

<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cp">#include</span> <span class="cpf">&lt;assert.h&gt;</span><span class="cp">
#include</span> <span class="cpf">&lt;wayland-server-core.h&gt;</span><span class="cp">
</span>
<span class="kt">int</span> <span class="nf">main</span><span class="p">(</span><span class="kt">void</span><span class="p">)</span> <span class="p">{</span>
    <span class="k">struct</span> <span class="n">wl_display</span> <span class="o">*</span><span class="n">wl_display</span> <span class="o">=</span> <span class="n">wl_display_create</span><span class="p">();</span>
    <span class="n">assert</span><span class="p">(</span><span class="n">wl_display</span><span class="p">);</span>

    <span class="k">return</span> <span class="mi">0</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>

<p>That piece of code doesn’t compile just yet. That’s because we didn’t specify any dependencies in
our Makefile. Let’s update it.</p>

<div class="language-makefile highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">DEPENDENCIES</span> <span class="o">=</span> wayland-server wlroots-0.19
<span class="nv">DEP_SRC_FLAGS</span> <span class="o">=</span> <span class="nf">$(</span><span class="nb">shell</span> pkg-config <span class="nt">--cflags</span> <span class="nv">$(DEPENDENCIES)</span><span class="nf">)</span>
<span class="nv">DEP_LIB_FLAGS</span> <span class="o">=</span> <span class="nf">$(</span><span class="nb">shell</span> pkg-config <span class="nt">--libs</span> <span class="nv">$(DEPENDENCIES)</span><span class="nf">)</span>

<span class="nv">FLAGS</span> <span class="o">=</span> <span class="nv">$(CFLAGS)</span> <span class="nv">$(DEP_SRC_FLAGS)</span> <span class="nv">$(DEP_LIB_FLAGS)</span>

<span class="c"># Our build script, we just make an out dir and compile our main.c file.
</span><span class="nl">build</span><span class="o">:</span>
	<span class="nb">mkdir</span> <span class="nt">-p</span> build
	clang src/main.c <span class="nv">$(FLAGS)</span>
</code></pre></div></div>

<p>We use <code class="language-plaintext highlighter-rouge">pkg-config</code> to get the compiler flags needed to compile using our dependencies. Unlike
other programming languages like Rust or Python, we don’t get a nice manifest with dependencies in
there, rather point the compiler to where those dependencies are in our system. <code class="language-plaintext highlighter-rouge">pkg-config</code> lets
us do that dynamically instead of hardcoding values.</p>

<p>Back to our display, we’re not accepting any incoming connections yet, though, so let’s start it.
Wayland connections come in through a socket, usually at <code class="language-plaintext highlighter-rouge">/run/user/1000/wayland-0</code> (replace 1000
with your Linux UID). I’m on GNOME shell, which means I’m already running a Wayland compositor, so
I’ll customize my socket’s name to <code class="language-plaintext highlighter-rouge">way-nyeki</code>.</p>

<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">const</span> <span class="kt">int</span> <span class="n">socket</span> <span class="o">=</span> <span class="n">wl_display_add_socket</span><span class="p">(</span><span class="n">wl_display</span><span class="p">,</span> <span class="s">"way-nyeki"</span><span class="p">);</span>
<span class="k">if</span> <span class="p">(</span><span class="n">socket</span> <span class="o">!=</span> <span class="mi">0</span><span class="p">)</span>
    <span class="k">return</span> <span class="mi">1</span><span class="p">;</span>
</code></pre></div></div>

<p>Now yes, time to start our compositor.</p>

<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cp">#define _POSIX_C_SOURCE 200112L  // setenv() is POSIX-only, we enable it with this flag
#include</span> <span class="cpf">&lt;stdlib.h&gt;</span><span class="cp">
</span><span class="c1">// -- snip --</span>

<span class="kt">int</span> <span class="nf">main</span><span class="p">(</span><span class="kt">void</span><span class="p">)</span> <span class="p">{</span>
    <span class="c1">// -- snip --</span>
    
    <span class="c1">// This env var lets clients know what socket to use to connect to the wayland compositor.</span>
    <span class="n">setenv</span><span class="p">(</span><span class="s">"WAYLAND_DISPLAY"</span><span class="p">,</span> <span class="s">"way-nyeki"</span><span class="p">,</span> <span class="nb">true</span><span class="p">);</span>
    <span class="n">wl_display_run</span><span class="p">(</span><span class="n">wl_display</span><span class="p">);</span>
    
    <span class="c1">// Once the compositor is done running, disconnect everyone cleanly.</span>
    <span class="n">wl_display_destroy_clients</span><span class="p">(</span><span class="n">wl_display</span><span class="p">);</span>
    <span class="n">wl_display_destroy</span><span class="p">(</span><span class="n">wl_display</span><span class="p">);</span>
<span class="p">}</span>
</code></pre></div></div>

<p>FINALLY. We got a compositor that runs, let’s run it! <code class="language-plaintext highlighter-rouge">make build</code> in your terminal will yield your
new compositor under <code class="language-plaintext highlighter-rouge">build</code>. Paste <code class="language-plaintext highlighter-rouge">build/compositor</code> to run it, and…</p>

<p>It does nothing. But hey, it doesn’t crash!</p>

<h2 id="rendering-something">Rendering Something</h2>

<p>Compositors are useless without rendering something, so let’s get to rendering such something.</p>

<p>Wlroots has a <code class="language-plaintext highlighter-rouge">wlr_backend</code> type which handles outputs and inputs. It supports 4 different
backends: DRM (directly to your screen), libinput (we won’t be touching inputs yet), wayland
(rendering our wayland compositor inside another wayland compositor, as a window), and x11 (same as
the wayland one but for x11).</p>

<p>Wlroots can automatically choose the backend we’ll need, so we’ll just do that. It requires a
reference to the event loop, so we’ll also grab it from our <code class="language-plaintext highlighter-rouge">wl_display</code>.</p>

<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// -- snip --</span>
<span class="cp">#include</span> <span class="cpf">&lt;wlr/backend.h&gt;</span><span class="cp">
</span>
<span class="kt">int</span> <span class="nf">main</span><span class="p">(</span><span class="kt">void</span><span class="p">)</span> <span class="p">{</span>
    <span class="c1">// -- snip --</span>
    <span class="n">assert</span><span class="p">(</span><span class="n">wl_display</span><span class="p">);</span>
    
    <span class="k">struct</span> <span class="n">wl_event_loop</span> <span class="o">*</span><span class="n">wl_event_loop</span> <span class="o">=</span> <span class="n">wl_display_get_event_loop</span><span class="p">(</span><span class="n">wl_display</span><span class="p">);</span>
    <span class="n">assert</span><span class="p">(</span><span class="n">wl_event_loop</span><span class="p">);</span>
    
    <span class="c1">// The NULL is a pointer to extra options. We don't need to pass any.</span>
    <span class="k">struct</span> <span class="n">wlr_backend</span> <span class="o">*</span><span class="n">wlr_backend</span> <span class="o">=</span> <span class="n">wlr_backend_autocreate</span><span class="p">(</span><span class="n">wl_event_loop</span><span class="p">,</span> <span class="nb">NULL</span><span class="p">);</span>
    <span class="n">assert</span><span class="p">(</span><span class="n">wlr_backend</span><span class="p">);</span>

    <span class="n">assert</span><span class="p">(</span><span class="n">wlr_backend_start</span><span class="p">(</span><span class="n">wlr_backend</span><span class="p">));</span>

    <span class="c1">// -- snip --</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Now, before we keep going like this, our compositor state is growing big, so let’s make a type to
hold it all.</p>

<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">struct</span> <span class="n">nye_server</span> <span class="p">{</span>
    <span class="c1">// Wayland stuff</span>
    <span class="k">struct</span> <span class="n">wl_display</span> <span class="o">*</span><span class="n">wl_display</span><span class="p">;</span>
    <span class="k">struct</span> <span class="n">wl_event_loop</span> <span class="o">*</span><span class="n">wl_event_loop</span><span class="p">;</span>

    <span class="c1">// Wlroots stuff</span>
    <span class="k">struct</span> <span class="n">wlr_backend</span> <span class="o">*</span><span class="n">wlr_backend</span><span class="p">;</span>
<span class="p">};</span>

<span class="kt">int</span> <span class="nf">main</span><span class="p">(</span><span class="kt">void</span><span class="p">)</span> <span class="p">{</span>
    <span class="k">struct</span> <span class="n">nye_server</span> <span class="n">nye_server</span> <span class="o">=</span> <span class="p">{</span><span class="mi">0</span><span class="p">};</span>

    <span class="n">nye_server</span><span class="p">.</span><span class="n">wl_display</span> <span class="o">=</span> <span class="n">wl_display_create</span><span class="p">();</span>
    <span class="n">assert</span><span class="p">(</span><span class="n">nye_server</span><span class="p">.</span><span class="n">wl_display</span><span class="p">);</span>

    <span class="n">nye_server</span><span class="p">.</span><span class="n">wl_event_loop</span> <span class="o">=</span> <span class="n">wl_display_get_event_loop</span><span class="p">(</span><span class="n">nye_server</span><span class="p">.</span><span class="n">wl_display</span><span class="p">);</span>
    <span class="n">assert</span><span class="p">(</span><span class="n">nye_server</span><span class="p">.</span><span class="n">wl_event_loop</span><span class="p">);</span>
    
    <span class="c1">// The NULL is a pointer to extra options. We don't need to pass any.</span>
    <span class="n">nye_server</span><span class="p">.</span><span class="n">wlr_backend</span> <span class="o">=</span> <span class="n">wlr_backend_autocreate</span><span class="p">(</span><span class="n">nye_server</span><span class="p">.</span><span class="n">wl_event_loop</span><span class="p">,</span> <span class="nb">NULL</span><span class="p">);</span>
    <span class="n">assert</span><span class="p">(</span><span class="n">nye_server</span><span class="p">.</span><span class="n">wlr_backend</span><span class="p">);</span>

    <span class="c1">// -- snip --</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Also update all references to those variables. Great.</p>

<h2 id="tracking-outputs">Tracking Outputs</h2>

<p>Now, before we can render something, there’s a topic I have to introduce: Outputs. In your normal
compositor-to-your-screen setup where the compositor is the one you’re actually using (and not a
nested one like we currently have), each output usually corresponds to a monitor. Since at the
moment we’re running out compositor nested inside another compositor, each output will correspond
to a window our compositor has within the wayland compositor.</p>

<p>Our backend will fire an event for us to listen to when an output is created, and another one when
that output is destroyed (window closed, monitor unplugged). Wlroots keeps some track of this
internally, yet we’ll also keep a reference to those output handles plus some state of ours to
store extra state for our compositor.</p>

<p>The wayland library uses linked lists to store stuff, so we’ll use those too. <code class="language-plaintext highlighter-rouge">wl_list</code> is the type
used for nodes within them. Let’s create one for our outputs, and also a wrapper output type.</p>

<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">struct</span> <span class="n">nye_server</span> <span class="p">{</span>
    <span class="c1">// -- snip --</span>

    <span class="c1">// Our stuff</span>
    <span class="k">struct</span> <span class="n">wl_list</span> <span class="n">nye_outputs</span><span class="p">;</span>
<span class="p">};</span>

<span class="k">struct</span> <span class="n">nye_output</span> <span class="p">{</span>
    <span class="k">struct</span> <span class="n">wl_list</span> <span class="n">link</span><span class="p">;</span>  <span class="c1">// nye_server::nye_outputs</span>

    <span class="k">struct</span> <span class="n">nye_server</span> <span class="o">*</span><span class="n">nye_server</span><span class="p">;</span>  <span class="c1">// Easy access to our compositor state.</span>
    <span class="k">struct</span> <span class="n">wlr_output</span> <span class="o">*</span><span class="n">wlr_output</span><span class="p">;</span>
<span class="p">};</span>
</code></pre></div></div>

<p>Now, let’s initialize our linked list.</p>

<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kt">int</span> <span class="nf">main</span><span class="p">(</span><span class="kt">void</span><span class="p">)</span> <span class="p">{</span>
    <span class="c1">// -- snip --</span>
    <span class="n">assert</span><span class="p">(</span><span class="n">nye_server</span><span class="p">.</span><span class="n">wlr_backend</span><span class="p">);</span>

    <span class="n">wl_list_init</span><span class="p">(</span><span class="o">&amp;</span><span class="n">nye_server</span><span class="p">.</span><span class="n">nye_outputs</span><span class="p">);</span>
    <span class="c1">// -- snip --</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Great! That’s an empty list, though. We need to add our outputs when they appear, so let’s add an
event listener for it.</p>

<p>Event listeners in wayland are wrapped in <code class="language-plaintext highlighter-rouge">wl_listner</code> types. We’ll call ours
<code class="language-plaintext highlighter-rouge">on_{resource}_{action}</code> (unless there’s no <code class="language-plaintext highlighter-rouge">{action}</code>) to make it clear which does what.</p>

<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// -- snip --</span>
<span class="cp">#include</span> <span class="cpf">&lt;stdio.h&gt;</span><span class="cp">
</span>
<span class="k">struct</span> <span class="n">nye_server</span> <span class="p">{</span>
    <span class="c1">// -- snip --</span>
    
    <span class="k">struct</span> <span class="n">wl_listener</span> <span class="n">on_output_add</span><span class="p">;</span>
<span class="p">};</span>

<span class="k">static</span> <span class="kt">void</span> <span class="nf">on_output_add</span><span class="p">(</span><span class="k">struct</span> <span class="n">wl_listener</span> <span class="o">*</span><span class="p">,</span> <span class="kt">void</span> <span class="o">*</span><span class="p">)</span> <span class="p">{</span>
    <span class="n">printf</span><span class="p">(</span><span class="s">"New output!</span><span class="se">\n</span><span class="s">"</span><span class="p">);</span>
<span class="p">}</span>

<span class="kt">int</span> <span class="nf">main</span><span class="p">(</span><span class="kt">void</span><span class="p">)</span> <span class="p">{</span>
    <span class="c1">// -- snip --</span>

    <span class="n">wl_list_init</span><span class="p">(</span><span class="o">&amp;</span><span class="n">nye_server</span><span class="p">.</span><span class="n">nye_outputs</span><span class="p">);</span>
    <span class="c1">// Set the wl_listener's callback to our new function.</span>
    <span class="n">nye_server</span><span class="p">.</span><span class="n">on_output_add</span><span class="p">.</span><span class="n">notify</span> <span class="o">=</span> <span class="n">on_output_add</span><span class="p">;</span>
    <span class="c1">// Hook our new listener to the backend's new_output event.</span>
    <span class="n">wl_signal_add</span><span class="p">(</span><span class="o">&amp;</span><span class="n">nye_server</span><span class="p">.</span><span class="n">wlr_backend</span><span class="o">-&gt;</span><span class="n">events</span><span class="p">.</span><span class="n">new_output</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">nye_server</span><span class="p">.</span><span class="n">on_output_add</span><span class="p">);</span>

    <span class="c1">// -- snip --</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Try running the compositor with <code class="language-plaintext highlighter-rouge">make build &amp;&amp; build/compositor</code>. You’ll now see the following gets
printed to the terminal:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>New output!
</code></pre></div></div>

<p>That new output represents our wayland window just created.</p>

<p>Now, the window is there but not rendering anything yet. It’s very possible that in a compositor
like Hyprland you don’t have a way of seeing it. In GNOME shell, nothing pops up on the screen, but
the application bar shows an extra empty VS Code window.</p>

<p>Now, back to our <code class="language-plaintext highlighter-rouge">on_output_add</code> function, we need to store new outputs in our
<code class="language-plaintext highlighter-rouge">nye_server::nye_outputs</code> list. We’ll first get a reference to our <code class="language-plaintext highlighter-rouge">nye_server</code> through the
<code class="language-plaintext highlighter-rouge">wl_listener</code> reference our callback gets and cast the <code class="language-plaintext highlighter-rouge">void *</code> to the original <code class="language-plaintext highlighter-rouge">wlr_output</code> type.</p>

<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">static</span> <span class="kt">void</span> <span class="nf">on_output_add</span><span class="p">(</span><span class="k">struct</span> <span class="n">wl_listener</span> <span class="o">*</span><span class="n">wl_listener</span><span class="p">,</span> <span class="kt">void</span> <span class="o">*</span><span class="n">data</span><span class="p">)</span> <span class="p">{</span>
    <span class="k">struct</span> <span class="n">nye_server</span> <span class="o">*</span><span class="n">nye_server</span> <span class="o">=</span> <span class="n">wl_container_of</span><span class="p">(</span><span class="n">wl_listener</span><span class="p">,</span> <span class="n">nye_server</span><span class="p">,</span> <span class="n">on_output_add</span><span class="p">);</span>
    <span class="k">struct</span> <span class="n">wlr_output</span> <span class="o">*</span><span class="n">wlr_output</span> <span class="o">=</span> <span class="n">data</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Ok but wait a sec, what’s <code class="language-plaintext highlighter-rouge">wl_container_of</code> doing here? Black magic, plain and simple. It’s said
that it uses some forgotten spells to get back <code class="language-plaintext highlighter-rouge">arg2</code> from <code class="language-plaintext highlighter-rouge">arg1</code> via <code class="language-plaintext highlighter-rouge">arg2::arg3</code>’s offset in
memory.</p>

<p>Anyway, now we have our wlroots output and our server, we can create our <code class="language-plaintext highlighter-rouge">nye_output</code> wrapper and
store it in our <code class="language-plaintext highlighter-rouge">nye_server</code>.</p>

<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">static</span> <span class="kt">void</span> <span class="nf">on_output_add</span><span class="p">(</span><span class="k">struct</span> <span class="n">wl_listener</span> <span class="o">*</span><span class="n">wl_listener</span><span class="p">,</span> <span class="kt">void</span> <span class="o">*</span><span class="n">data</span><span class="p">)</span> <span class="p">{</span>
    <span class="c1">// -- snip --</span>

    <span class="c1">// It's gonna outlive the function's call, so it goes to the heap.</span>
    <span class="k">struct</span> <span class="n">nye_output</span> <span class="o">*</span><span class="n">nye_output</span> <span class="o">=</span> <span class="n">calloc</span><span class="p">(</span><span class="mi">1</span><span class="p">,</span> <span class="k">sizeof</span><span class="p">(</span><span class="k">struct</span> <span class="n">nye_output</span><span class="p">));</span>
    <span class="n">nye_output</span><span class="o">-&gt;</span><span class="n">nye_server</span> <span class="o">=</span> <span class="n">nye_server</span><span class="p">;</span>
    <span class="n">nye_output</span><span class="o">-&gt;</span><span class="n">wlr_output</span> <span class="o">=</span> <span class="n">wlr_output</span><span class="p">;</span>
    <span class="c1">// Connect em `wl_list`s.</span>
    <span class="n">wl_list_insert</span><span class="p">(</span><span class="o">&amp;</span><span class="n">nye_server</span><span class="o">-&gt;</span><span class="n">nye_outputs</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">nye_output</span><span class="o">-&gt;</span><span class="n">link</span><span class="p">);</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Great! Now we have them tracked.</p>

<h2 id="output-modes">Output Modes</h2>

<p>As we said before, wayland outputs are usually monitors. Such monitors have properties like refresh
rate, height and width, and other properties. Before we can use our outputs for apps, we need to do
two things:</p>

<ol>
  <li>To set the output’s mode (outputs can have many supported ones or none)</li>
  <li>To create the <code class="language-plaintext highlighter-rouge">wl_output</code> global for apps to use those monitors.</li>
</ol>

<p>Wlroots makes this dead simple, starting with setting the mode:</p>

<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">static</span> <span class="kt">void</span> <span class="nf">on_output_add</span><span class="p">(</span><span class="k">struct</span> <span class="n">wl_listener</span> <span class="o">*</span><span class="n">wl_listener</span><span class="p">,</span> <span class="kt">void</span> <span class="o">*</span><span class="n">data</span><span class="p">)</span> <span class="p">{</span>
    <span class="c1">// -- snip --</span>
    <span class="n">wl_signal_add</span><span class="p">(</span><span class="o">&amp;</span><span class="n">nye_server</span><span class="p">.</span><span class="n">wlr_backend</span><span class="o">-&gt;</span><span class="n">events</span><span class="p">.</span><span class="n">new_output</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">nye_server</span><span class="p">.</span><span class="n">on_output_add</span><span class="p">);</span>
    
    <span class="k">struct</span> <span class="n">wlr_output_state</span> <span class="n">wlr_output_state</span><span class="p">;</span>
    <span class="n">wlr_output_state_init</span><span class="p">(</span><span class="o">&amp;</span><span class="n">wlr_output_state</span><span class="p">);</span>
    <span class="n">wlr_output_state_set_enabled</span><span class="p">(</span><span class="o">&amp;</span><span class="n">wlr_output_state</span><span class="p">,</span> <span class="nb">true</span><span class="p">);</span>

    <span class="c1">// Nested wayland outputs don't have modes. We commit an empty state if that's the case.</span>
    <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">wl_list_empty</span><span class="p">(</span><span class="o">&amp;</span><span class="n">wlr_output</span><span class="o">-&gt;</span><span class="n">modes</span><span class="p">))</span>
    <span class="p">{</span>
        <span class="c1">// Outputs can have many supported modes, we pick the preferred one (biggest, fastest).</span>
        <span class="k">struct</span> <span class="n">wlr_output_mode</span> <span class="o">*</span><span class="n">wlr_output_mode</span> <span class="o">=</span> <span class="n">wlr_output_preferred_mode</span><span class="p">(</span><span class="n">wlr_output</span><span class="p">);</span>
        <span class="n">wlr_output_state_set_mode</span><span class="p">(</span><span class="o">&amp;</span><span class="n">wlr_output_state</span><span class="p">,</span> <span class="n">wlr_output_mode</span><span class="p">);</span>
    <span class="p">}</span>

    <span class="n">wlr_output_commit_state</span><span class="p">(</span><span class="n">wlr_output</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">wlr_output_state</span><span class="p">);</span>
    <span class="n">wlr_output_state_finish</span><span class="p">(</span><span class="o">&amp;</span><span class="n">wlr_output_state</span><span class="p">);</span>

    <span class="k">struct</span> <span class="n">nye_output</span> <span class="o">*</span><span class="n">nye_output</span> <span class="o">=</span> <span class="n">calloc</span><span class="p">(</span><span class="mi">1</span><span class="p">,</span> <span class="k">sizeof</span><span class="p">(</span><span class="k">struct</span> <span class="n">nye_output</span><span class="p">));</span>
    <span class="c1">// -- snip --</span>
<span class="p">}</span>
</code></pre></div></div>

<p>And at the end of our handler function, we add this line to create the global:</p>

<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">wlr_output_create_global</span><span class="p">(</span><span class="n">wlr_output</span><span class="p">,</span> <span class="n">nye_server</span><span class="o">-&gt;</span><span class="n">wl_display</span><span class="p">);</span>
</code></pre></div></div>

<p>Great! This is a bit of code that won’t produce any visible changes, but it’s the foundation for
our rendering in a minute.</p>

<p>Last but not least, our code currently leaks memory. We store outputs when they’re created, but we
don’t free that memory when an output is destroyed. Let’s create a listener that takes care of
that.</p>

<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">struct</span> <span class="n">nye_output</span> <span class="p">{</span>
    <span class="c1">// -- snip --</span>
    <span class="k">struct</span> <span class="n">wl_listener</span> <span class="n">on_output_del</span><span class="p">;</span>
<span class="p">};</span>

<span class="k">static</span> <span class="kt">void</span> <span class="nf">on_output_del</span><span class="p">(</span><span class="k">struct</span> <span class="n">wl_listener</span> <span class="o">*</span><span class="n">wl_listener</span><span class="p">,</span> <span class="kt">void</span> <span class="o">*</span><span class="p">)</span> <span class="p">{</span>
    <span class="c1">// More memory dark magic.</span>
    <span class="k">struct</span> <span class="n">nye_output</span> <span class="o">*</span><span class="n">nye_output</span> <span class="o">=</span> <span class="n">wl_container_of</span><span class="p">(</span><span class="n">wl_listener</span><span class="p">,</span> <span class="n">nye_output</span><span class="p">,</span> <span class="n">on_output_del</span><span class="p">);</span>

    <span class="c1">// Remove the output from the linked list and unlink our handler.</span>
    <span class="n">wl_list_remove</span><span class="p">(</span><span class="o">&amp;</span><span class="n">nye_output</span><span class="o">-&gt;</span><span class="n">link</span><span class="p">);</span>
    <span class="n">wl_list_remove</span><span class="p">(</span><span class="o">&amp;</span><span class="n">nye_output</span><span class="o">-&gt;</span><span class="n">on_output_del</span><span class="p">.</span><span class="n">link</span><span class="p">);</span>

    <span class="n">free</span><span class="p">(</span><span class="n">nye_output</span><span class="p">);</span>
<span class="p">}</span>

<span class="k">static</span> <span class="kt">void</span> <span class="nf">on_output_add</span><span class="p">(</span><span class="k">struct</span> <span class="n">wl_listener</span> <span class="o">*</span><span class="n">wl_listener</span><span class="p">,</span> <span class="kt">void</span> <span class="o">*</span><span class="n">data</span><span class="p">)</span> <span class="p">{</span>
    <span class="c1">// -- snip --</span>
    <span class="n">nye_output</span><span class="o">-&gt;</span><span class="n">wlr_output</span> <span class="o">=</span> <span class="n">wlr_output</span><span class="p">;</span>

    <span class="n">nye_output</span><span class="o">-&gt;</span><span class="n">on_output_del</span><span class="p">.</span><span class="n">notify</span> <span class="o">=</span> <span class="n">on_output_del</span><span class="p">;</span>
    <span class="n">wl_signal_add</span><span class="p">(</span><span class="o">&amp;</span><span class="n">wlr_output</span><span class="o">-&gt;</span><span class="n">events</span><span class="p">.</span><span class="n">destroy</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">nye_output</span><span class="o">-&gt;</span><span class="n">on_output_del</span><span class="p">);</span>

    <span class="n">wl_list_insert</span><span class="p">(</span><span class="o">&amp;</span><span class="n">nye_server</span><span class="o">-&gt;</span><span class="n">nye_outputs</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">nye_output</span><span class="o">-&gt;</span><span class="n">link</span><span class="p">);</span>
    <span class="c1">// -- snip --</span>
<span class="p">}</span>
</code></pre></div></div>

<h2 id="rendering-something-now-yes">Rendering Something (Now Yes)</h2>

<p>Unfortunately, to render apps we still have some chunk (we need to implement XDG and I haven’t yet
learned to do that). BUT, don’t leave me yet, we’ll make our window render some colors to see if
we’ve done everything correctly up to now.</p>

<p>In an unhappy world, we’d need to do OpenGL/Vulkan rendering by hand. Since this is a world that’s
bright and full of love, though, wlroots gives us a nice API that handles all of the ugly part
without us having to touch it.</p>

<p>Wlroots’s Scene API allows us to create a tree of nodes, which can be surfaces or rects, to draw
on a screen without handling all the raw graphics ourselves. We create a <code class="language-plaintext highlighter-rouge">wlr_scene</code> for our whole
compositor, then one or more <code class="language-plaintext highlighter-rouge">wlr_scene_tree</code> to place our drawables in. For example, we could have
a tree per monitor, or a tree per workspace, or even both combined.</p>

<p>We’ll keep it simple and go for a single tree (the one that comes by default with <code class="language-plaintext highlighter-rouge">wlr_scene</code>) for
everything to begin with. Let’s create a new scene and store it in our server state.</p>

<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// -- snip --</span>
<span class="cp">#include</span> <span class="cpf">&lt;wlr/types/wlr_scene.h&gt;</span><span class="cp">
</span>
<span class="k">struct</span> <span class="n">nye_server</span> <span class="p">{</span>
    <span class="c1">// -- snip --</span>
    <span class="k">struct</span> <span class="n">wlr_scene</span> <span class="o">*</span><span class="n">wlr_scene</span><span class="p">;</span>
    <span class="c1">// -- snip --</span>
<span class="p">};</span>

<span class="kt">int</span> <span class="nf">main</span><span class="p">(</span><span class="kt">void</span><span class="p">)</span> <span class="p">{</span>
    <span class="c1">// -- snip --</span>
    <span class="n">assert</span><span class="p">(</span><span class="n">nye_server</span><span class="p">.</span><span class="n">wlr_backend</span><span class="p">);</span>

    <span class="n">nye_server</span><span class="p">.</span><span class="n">wlr_scene</span> <span class="o">=</span> <span class="n">wlr_scene_create</span><span class="p">();</span>
    <span class="n">assert</span><span class="p">(</span><span class="n">nye_server</span><span class="p">.</span><span class="n">wlr_scene</span><span class="p">);</span>
    <span class="c1">// -- snip --</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Outputs need to be made available for our scene, so lets create the wrapping <code class="language-plaintext highlighter-rouge">wlr_scene_output</code> and
store it in our <code class="language-plaintext highlighter-rouge">nye_output</code>. We’ll also destroy it when the output is deleted.</p>

<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">struct</span> <span class="n">nye_output</span> <span class="p">{</span>
    <span class="c1">// -- snip --</span>
    <span class="k">struct</span> <span class="n">wlr_scene_output</span> <span class="o">*</span><span class="n">wlr_scene_output</span><span class="p">;</span>
    <span class="c1">// -- snip --</span>
<span class="p">};</span>

<span class="k">static</span> <span class="kt">void</span> <span class="nf">on_output_del</span><span class="p">(</span><span class="k">struct</span> <span class="n">wl_listener</span> <span class="o">*</span><span class="n">wl_listener</span><span class="p">,</span> <span class="kt">void</span> <span class="o">*</span><span class="p">)</span> <span class="p">{</span>
    <span class="c1">// -- snip --</span>
    <span class="n">wl_list_remove</span><span class="p">(</span><span class="o">&amp;</span><span class="n">nye_output</span><span class="o">-&gt;</span><span class="n">on_output_del</span><span class="p">.</span><span class="n">link</span><span class="p">);</span>

    <span class="n">wlr_scene_output_destroy</span><span class="p">(</span><span class="n">nye_output</span><span class="o">-&gt;</span><span class="n">wlr_scene_output</span><span class="p">);</span>

    <span class="n">free</span><span class="p">(</span><span class="n">nye_output</span><span class="p">);</span>
<span class="p">}</span>

<span class="k">static</span> <span class="kt">void</span> <span class="nf">on_output_add</span><span class="p">(</span><span class="k">struct</span> <span class="n">wl_listener</span> <span class="o">*</span><span class="n">wl_listener</span><span class="p">,</span> <span class="kt">void</span> <span class="o">*</span><span class="n">data</span><span class="p">)</span> <span class="p">{</span>
    <span class="c1">// -- snip --</span>
    <span class="n">nye_output</span><span class="o">-&gt;</span><span class="n">wlr_output</span> <span class="o">=</span> <span class="n">wlr_output</span><span class="p">;</span>

    <span class="n">nye_output</span><span class="o">-&gt;</span><span class="n">wlr_scene_output</span> <span class="o">=</span> <span class="n">wlr_scene_output_create</span><span class="p">(</span><span class="n">nye_server</span><span class="o">-&gt;</span><span class="n">wlr_scene</span><span class="p">,</span> <span class="n">wlr_output</span><span class="p">);</span>
    <span class="c1">// -- snip --</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Great! Now, we still haven’t made our scene render, so let’s get to that.</p>

<p>If we were handling rendering by hand, we’d need to handle synchronization, buffer rotation, and
many ugly parts of it. However, wlroots makes our life full of happiness and rainbows by giving us
an <code class="language-plaintext highlighter-rouge">on_frame</code> event callback we can use to render without worrying about all that.</p>

<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">struct</span> <span class="n">nye_output</span> <span class="p">{</span>
    <span class="c1">// -- snip --</span>
    <span class="k">struct</span> <span class="n">wl_listener</span> <span class="n">on_frame</span><span class="p">;</span>
    <span class="c1">// -- snip --</span>
<span class="p">};</span>

<span class="k">static</span> <span class="kt">void</span> <span class="nf">on_frame</span><span class="p">(</span><span class="k">struct</span> <span class="n">wl_listener</span> <span class="o">*</span><span class="p">,</span> <span class="kt">void</span> <span class="o">*</span><span class="p">)</span> <span class="p">{}</span>

<span class="k">static</span> <span class="kt">void</span> <span class="nf">on_output_del</span><span class="p">(</span><span class="k">struct</span> <span class="n">wl_listener</span> <span class="o">*</span><span class="n">wl_listener</span><span class="p">,</span> <span class="kt">void</span> <span class="o">*</span><span class="p">)</span> <span class="p">{</span>
    <span class="c1">// -- snip --</span>
    <span class="n">wl_list_remove</span><span class="p">(</span><span class="o">&amp;</span><span class="n">nye_output</span><span class="o">-&gt;</span><span class="n">link</span><span class="p">);</span>

    <span class="n">wl_list_remove</span><span class="p">(</span><span class="o">&amp;</span><span class="n">nye_output</span><span class="o">-&gt;</span><span class="n">on_frame</span><span class="p">.</span><span class="n">link</span><span class="p">);</span>
    
    <span class="n">wl_list_remove</span><span class="p">(</span><span class="o">&amp;</span><span class="n">nye_output</span><span class="o">-&gt;</span><span class="n">on_output_del</span><span class="p">.</span><span class="n">link</span><span class="p">);</span>
    <span class="c1">// -- snip --</span>
<span class="p">}</span>

<span class="k">static</span> <span class="kt">void</span> <span class="nf">on_output_add</span><span class="p">(</span><span class="k">struct</span> <span class="n">wl_listener</span> <span class="o">*</span><span class="n">wl_listener</span><span class="p">,</span> <span class="kt">void</span> <span class="o">*</span><span class="n">data</span><span class="p">)</span> <span class="p">{</span>
    <span class="c1">// -- snip --</span>
    <span class="n">nye_output</span><span class="o">-&gt;</span><span class="n">on_frame</span><span class="p">.</span><span class="n">notify</span> <span class="o">=</span> <span class="n">on_frame</span><span class="p">;</span>
    <span class="n">wl_signal_add</span><span class="p">(</span><span class="o">&amp;</span><span class="n">wlr_output</span><span class="o">-&gt;</span><span class="n">events</span><span class="p">.</span><span class="n">frame</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">nye_output</span><span class="o">-&gt;</span><span class="n">on_frame</span><span class="p">);</span>

    <span class="n">nye_output</span><span class="o">-&gt;</span><span class="n">on_output_del</span><span class="p">.</span><span class="n">notify</span> <span class="o">=</span> <span class="n">on_output_del</span><span class="p">;</span>
    <span class="c1">// -- snip --</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Now yes, once an output is created, <code class="language-plaintext highlighter-rouge">on_frame()</code> will start getting called every time we have to
draw.</p>

<p>NOW YES! Time for the part where we actually get something drawn on our screens. Our <code class="language-plaintext highlighter-rouge">on_frame</code>
just needs to call <code class="language-plaintext highlighter-rouge">wlr_scene_output_commit()</code> and send done callbacks to clients when the frame is
done being rendered.</p>

<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">static</span> <span class="kt">void</span> <span class="nf">on_frame</span><span class="p">(</span><span class="k">struct</span> <span class="n">wl_listener</span> <span class="o">*</span><span class="n">wl_listener</span><span class="p">,</span> <span class="kt">void</span> <span class="o">*</span><span class="p">)</span> <span class="p">{</span>
    <span class="c1">// More black magic!</span>
    <span class="k">struct</span> <span class="n">nye_output</span> <span class="o">*</span><span class="n">nye_output</span> <span class="o">=</span> <span class="n">wl_container_of</span><span class="p">(</span><span class="n">wl_listener</span><span class="p">,</span> <span class="n">nye_output</span><span class="p">,</span> <span class="n">on_frame</span><span class="p">);</span>
    
    <span class="c1">// Draw everything our scene output has pending to the actual output.</span>
    <span class="n">wlr_scene_output_commit</span><span class="p">(</span><span class="n">nye_output</span><span class="o">-&gt;</span><span class="n">wlr_scene_output</span><span class="p">,</span> <span class="nb">NULL</span><span class="p">);</span>

    <span class="c1">// Once our frame is drawn, we let our clients know so that they can start drawing their next</span>
    <span class="c1">// frame.</span>
    <span class="k">struct</span> <span class="n">timespec</span> <span class="n">now</span><span class="p">;</span>
    <span class="n">clock_gettime</span><span class="p">(</span><span class="n">CLOCK_MONOTONIC</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">now</span><span class="p">);</span>
    <span class="n">wlr_scene_output_send_frame_done</span><span class="p">(</span><span class="n">nye_output</span><span class="o">-&gt;</span><span class="n">wlr_scene_output</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">now</span><span class="p">);</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Okay, finally, our time has come. Compile, run, and…</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>New output!
compositor: types/output/swapchain.c:15: create_swapchain: Assertion `output-&gt;allocator != NULL' failed.
Aborted                    build/compositor
</code></pre></div></div>

<p>No please don’t leave yet I’m begging. Happens to be that rendering in wlroots requires a renderer
and an allocator. They’re easy to create, yet we didn’t so this failed. Let’s create them quickly.</p>

<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// -- snip --</span>
<span class="cp">#include</span> <span class="cpf">&lt;wlr/render/allocator.h&gt;</span><span class="cp">
#include</span> <span class="cpf">&lt;wlr/render/wlr_renderer.h&gt;</span><span class="cp">
</span>
<span class="k">struct</span> <span class="n">nye_server</span> <span class="p">{</span>
    <span class="c1">// -- snip --</span>
    <span class="k">struct</span> <span class="n">wlr_renderer</span> <span class="o">*</span><span class="n">wlr_renderer</span><span class="p">;</span>
    <span class="k">struct</span> <span class="n">wlr_allocator</span> <span class="o">*</span><span class="n">wlr_allocator</span><span class="p">;</span>
    <span class="c1">// -- snip --</span>
<span class="p">};</span>

<span class="k">static</span> <span class="kt">void</span> <span class="nf">on_output_add</span><span class="p">(</span><span class="k">struct</span> <span class="n">wl_listener</span> <span class="o">*</span><span class="n">wl_listener</span><span class="p">,</span> <span class="kt">void</span> <span class="o">*</span><span class="n">data</span><span class="p">)</span> <span class="p">{</span>
    <span class="c1">// -- snip --</span>
    <span class="n">wlr_output_state_set_enabled</span><span class="p">(</span><span class="o">&amp;</span><span class="n">wlr_output_state</span><span class="p">,</span> <span class="nb">true</span><span class="p">);</span>

    <span class="n">wlr_output_init_render</span><span class="p">(</span><span class="n">wlr_output</span><span class="p">,</span> <span class="n">nye_server</span><span class="o">-&gt;</span><span class="n">wlr_allocator</span><span class="p">,</span> <span class="n">nye_server</span><span class="o">-&gt;</span><span class="n">wlr_renderer</span><span class="p">);</span>
    <span class="c1">// -- snip --</span>
<span class="p">}</span>

<span class="kt">int</span> <span class="nf">main</span><span class="p">(</span><span class="kt">void</span><span class="p">)</span> <span class="p">{</span>
    <span class="c1">// -- snip --</span>
    <span class="n">assert</span><span class="p">(</span><span class="n">nye_server</span><span class="p">.</span><span class="n">wlr_scene</span><span class="p">);</span>

    <span class="n">nye_server</span><span class="p">.</span><span class="n">wlr_renderer</span> <span class="o">=</span> <span class="n">wlr_renderer_autocreate</span><span class="p">(</span><span class="n">nye_server</span><span class="p">.</span><span class="n">wlr_backend</span><span class="p">);</span>
    <span class="n">assert</span><span class="p">(</span><span class="n">nye_server</span><span class="p">.</span><span class="n">wlr_renderer</span><span class="p">);</span>
    <span class="n">nye_server</span><span class="p">.</span><span class="n">wlr_allocator</span> <span class="o">=</span> <span class="n">wlr_allocator_autocreate</span><span class="p">(</span><span class="n">nye_server</span><span class="p">.</span><span class="n">wlr_backend</span><span class="p">,</span> <span class="n">nye_server</span><span class="p">.</span><span class="n">wlr_renderer</span><span class="p">);</span>
    <span class="n">assert</span><span class="p">(</span><span class="n">nye_server</span><span class="p">.</span><span class="n">wlr_allocator</span><span class="p">);</span>

    <span class="c1">// -- snip --</span>
<span class="p">}</span>
</code></pre></div></div>

<p>NOW YES! I PROMISE THIS TIME IT’LL WORK. Compile, run, and…</p>

<p>IT WORKS! A black screen gets displayed in your screen. Apps can’t render anything yet, but we went
from an invisible window to something shown in our screens, great step!</p>

<p>I said we’d render some colors in our screen, so let’s get to that.</p>

<h2 id="rendering-colors">Rendering Colors</h2>

<p>We’ll render a red square to test whether our rendering is actually working. This will be a rect in
our scene which we’ll create after creating the scene object, in our <code class="language-plaintext highlighter-rouge">main()</code>.</p>

<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kt">int</span> <span class="nf">main</span><span class="p">(</span><span class="kt">void</span><span class="p">)</span> <span class="p">{</span>
    <span class="c1">// -- snip --</span>
    <span class="n">assert</span><span class="p">(</span><span class="n">nye_server</span><span class="p">.</span><span class="n">wlr_allocator</span><span class="p">);</span>

    <span class="kt">float</span> <span class="n">color</span><span class="p">[</span><span class="mi">4</span><span class="p">]</span> <span class="o">=</span> <span class="p">{</span><span class="mi">1</span><span class="p">.</span><span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">.</span><span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">.</span><span class="mi">0</span><span class="p">,</span> <span class="mi">1</span><span class="p">.</span><span class="mi">0</span><span class="p">};</span>  <span class="c1">// RGBA</span>
    <span class="k">struct</span> <span class="n">wlr_scene_rect</span><span class="o">*</span> <span class="n">wlr_scene_rect</span> <span class="o">=</span> <span class="n">wlr_scene_rect_create</span><span class="p">(</span>
        <span class="o">&amp;</span><span class="n">nye_server</span><span class="p">.</span><span class="n">wlr_scene</span><span class="o">-&gt;</span><span class="n">tree</span><span class="p">,</span>
        <span class="mi">100</span><span class="p">,</span>
        <span class="mi">100</span><span class="p">,</span>
        <span class="n">color</span>
    <span class="p">);</span>
    <span class="n">wlr_scene_node_set_position</span><span class="p">(</span><span class="o">&amp;</span><span class="n">wlr_scene_rect</span><span class="o">-&gt;</span><span class="n">node</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">);</span>
    <span class="c1">// -- snip --</span>
<span class="p">}</span>
</code></pre></div></div>

<p>When you re-compile and re-run the compositor, you’ll see that a 100x100 red sequare is now shown
in the compositor’s window.</p>

<h2 id="exitting-the-compositor">Exitting the Compositor</h2>

<p>You may have noticed that when you close the compositor’s window instead of <code class="language-plaintext highlighter-rouge">Ctrl + C</code>-ing the
program, the program will keep running. This is because the compositor doesn’t depend on a window
to be running. To exit the compositor, use <code class="language-plaintext highlighter-rouge">Ctrl + C</code> in your terminal.</p>

<p>That’s it for now! The next part of this series will cover how to get apps to draw windows in our
compositor. That’ll require to implement XDG, which I haven’t investigated in depth yet.</p>

<p>If you have found any bugs in the code I showed, or have any suggestions, feel free to <a href="https://github.com/Nekidev/nekidev.github.io">open an
issue in GitHub</a>. Good luck!</p>]]></content><author><name>Nyeki</name></author><summary type="html"><![CDATA[Part two of the Welcome to Wlroots! series. If you haven’t yet, you should go read the part 1 of this series.]]></summary></entry><entry><title type="html">Four Hours Before my First Launch</title><link href="https://nekidev.github.io/2026/11/03/1-four-hours-before-my-first-launch.html" rel="alternate" type="text/html" title="Four Hours Before my First Launch" /><published>2026-11-03T00:00:00+00:00</published><updated>2026-11-03T00:00:00+00:00</updated><id>https://nekidev.github.io/2026/11/03/1-four-hours-before-my-first-launch</id><content type="html" xml:base="https://nekidev.github.io/2026/11/03/1-four-hours-before-my-first-launch.html"><![CDATA[<p>Today I finished my application to YC. Took me two days and a bunch of attempts to get the founder
video right since my spoken English needed some practice, yet after actually following the bullet
point thing YC tells you to do it was way easier.</p>

<p>I also made the Product Hunt launch draft for <a href="https://duckity.com/">Duckity</a> for tomorrow (actually today, it’s 12:31
AM) and attempted at doing the same with TinyLaunch but their site was broken. I’ll wake up early
tomorrow to post it in Hacker News and upload it to all the other sites in the list that I did not
put Duckity in today. I also started writing about it in my new <a href="https://dev-null.sh/@Nekidev">/dev/null account</a>.</p>

<p>Something I notice is that in my head there’s this worry of this not being a strong enough problem,
but every now and then I see someone complain about it. E.g. today. Reading a Paul Graham blog post
on how to start a startup I learned, more like re-realized, that Duckity may be too complex. That’s
why the next code thing I’ll be working on will be adding a few templates to get started with
instead of making people configure things from scratch.</p>

<p>I have a little amount of worries about things, but none is going to stop me from launching as many
times as I need to launch, each time improving from the past time.</p>

<p>There’s not that much to write in my head today, only that I really hope I get that YC interview
but that I’ll be working like there’s no YC in the meantime. My plan for the next few days:</p>

<ul>
  <li>Launch on Product Hunt</li>
  <li>Launch on Hacker News</li>
  <li>Figure out TinyLaunch</li>
  <li>Keep posting about it on /dev/null</li>
  <li>Grab that Hacker News list of sites to post your product in and make noise</li>
  <li>Launch again this Friday, since Product Hunt is making it a day where you can relaunch many times</li>
</ul>

<p>Also, I was feeling this anxiety I could not tell what was. I was feeling like I was less because I
saw all those guys (Garry Tan and friends) being so successful and having done so much and doing so
well and knowing so much and that demotivated me, but I realized that them having that knowledge
and experience doesn’t mean I can’t have it too. Anxiety vanished.</p>

<p>I’m also thinking of more and more things to scale Duckity. These will include:</p>

<ul>
  <li>Global scaling, since now Duckity is a regional cluster thing,</li>
  <li>Adding teams for organizations to manage apps,</li>
  <li>Adding easy benchmarking for developers to know what speeds VDFs will solve at in each device,</li>
  <li>SELF-HOSTING!</li>
  <li>React Native SDKs,</li>
  <li>A bunch of templates to pick from,</li>
  <li>A default basic policy when creating apps</li>
</ul>

<p>They’re not listed in order of importance. Also, the Duckity pro pricing is listed at $49/mo yet
I’m worried that’s too high for a developer tool, so I gave the Product Hunt launch an 80% discount
code for the first 100 users (holy discount innit) for the first 6 months.</p>

<p>I’m looking forward to making everything go great! First launch of as many as needed. Good luck
with whatever you’re building!</p>]]></content><author><name>Nyeki</name></author><summary type="html"><![CDATA[Today I finished my application to YC. Took me two days and a bunch of attempts to get the founder video right since my spoken English needed some practice, yet after actually following the bullet point thing YC tells you to do it was way easier.]]></summary></entry><entry><title type="html">Duckity’s Credibility Problem</title><link href="https://nekidev.github.io/2026/11/03/2-duckitys-credibility-problem.html" rel="alternate" type="text/html" title="Duckity’s Credibility Problem" /><published>2026-11-03T00:00:00+00:00</published><updated>2026-11-03T00:00:00+00:00</updated><id>https://nekidev.github.io/2026/11/03/2-duckitys-credibility-problem</id><content type="html" xml:base="https://nekidev.github.io/2026/11/03/2-duckitys-credibility-problem.html"><![CDATA[<p>Quick update on <a href="/2026/11/03/1-four-hours-before-my-first-launch.html">my last blog post</a>, I realized Duckity’s first problem is not gonna be anything
other than credibility. The landing page in its current state does not much at all to establish
credibility. People click on my links, visit my website, but they leave without signing up.</p>

<p>I had a user tell me that they would not test Duckity because they didn’t know whether it worked.
The two problems I had addressing this properly were simple:</p>

<ol>
  <li>I thought I had little way to prove credibility since I had little security background,</li>
  <li>In Payphone (past successful project of mine), I knew users were onto something when more than
one of them brought up the same issue. This was only one guy, so I was quicker to filter it out.</li>
</ol>

<p>I’m still on time to fix it, yet I’m also going to have to be asleep soon since tomorrow I have to
wake up early to launch Duckity on Hacker News and review Product Hunt comments.</p>

<p>About the problem more in detail, the landing page has a section that speaks about how it works,
but it does little to dive into any more details. Move that to the very top of my priority things
to work on, it’s getting tackled tomorrow first thing.</p>]]></content><author><name>Nyeki</name></author><summary type="html"><![CDATA[Quick update on my last blog post, I realized Duckity’s first problem is not gonna be anything other than credibility. The landing page in its current state does not much at all to establish credibility. People click on my links, visit my website, but they leave without signing up.]]></summary></entry><entry><title type="html">Launch Failed - What Went Wrong</title><link href="https://nekidev.github.io/2026/11/03/3-launch-failed-what-went-wrong.html" rel="alternate" type="text/html" title="Launch Failed - What Went Wrong" /><published>2026-11-03T00:00:00+00:00</published><updated>2026-11-03T00:00:00+00:00</updated><id>https://nekidev.github.io/2026/11/03/3-launch-failed-what-went-wrong</id><content type="html" xml:base="https://nekidev.github.io/2026/11/03/3-launch-failed-what-went-wrong.html"><![CDATA[<p>Woke up at 10 AM to post in Hacker News, checked how the Product Hunt launch went.</p>

<p>Zero. None. Nothing. No signups, no comments, no upvotes, and 1-2 visitors an hour. Steady, but
that’s it.</p>

<p>So, time to figure out what went wrong. And I barely got any clicks to the Product Hunt launch,
which tells me I likely had an issue with either discoverability or the tag line.</p>

<p>Product Hunt had at least 660 product launches today. Duckity is #660, that’s how I know. They
recommend not asking people for upvotes, otherwise you’ll be flamed, so I was a good boy and didn’t
overtly promote it everywhere. Mistake #1, bring your people with you beforehand.</p>

<p>I had posted about what I’m doing yesterday in /dev/null. Not enough. Without an actual follower
base or hype, they won’t come after you next day, so I have to build a little follower base to get
me started before the next launch.</p>

<p>I am getting clicks on /dev/null and twitter, and my cold emails got clicks too. Yet zero replies?
Actually, my cold emails got the most clicks out of them all. When I wrote them by hand, obviously.
The only opinion I got on Duckity beforehand was that they didn’t know whether it worked, and
that’s a fair complaint. I’ll be working on it as I said on my earlier blog posts.</p>

<p>Also, I’m thinking credibility may be that issue stopping people from answering my emails.</p>

<p>OKAY WAIT. The Hacker News launch is giving me a spike of clicks, so I’ll post an update if
something interesting happens. If nothing gets posted, assume nothing happened.</p>

<p>Either way, I’m starving and these are the things I’ll be working on next:</p>

<ul>
  <li>Going to get developers by hand</li>
  <li>Fixing the credibility problem, as talked about earlier today</li>
</ul>

<p>Good luck with whatever you’re building!</p>

<p>P.S. I’ve read somewhere within YC that a founder who has time to blog doesn’t have time for their
company. There’s some sort of truth on that, I gotta start working x2 now that I lowered anxiety.</p>]]></content><author><name>Nyeki</name></author><summary type="html"><![CDATA[Woke up at 10 AM to post in Hacker News, checked how the Product Hunt launch went.]]></summary></entry><entry><title type="html">I Applied to YC</title><link href="https://nekidev.github.io/2026/11/01/i-applied-to-yc.html" rel="alternate" type="text/html" title="I Applied to YC" /><published>2026-11-01T00:00:00+00:00</published><updated>2026-11-01T00:00:00+00:00</updated><id>https://nekidev.github.io/2026/11/01/i-applied-to-yc</id><content type="html" xml:base="https://nekidev.github.io/2026/11/01/i-applied-to-yc.html"><![CDATA[<p>It’s 1:57 AM Sunday morning, I’m launching Duckity on Tuesday. I don’t even know how I got to YC’s
site, I was likely geeking around the internet. Thing is, 2 days remained for applications to
close, way enough time to fill out an application form, so I went ahead and did it. At the time of
writing, I’m only missing to fill up the founder video and the product demo video. I’ll do those
either tomorrow or on Monday.</p>

<p>Now, I’m biting my nails out of anxiety right now. A bit of context, I’m living with my parents,
they have told me they’ll kick me out when I turn 21 (March 26th), and this startup is all my plan
for life. If it fails? New startup. And the next one. And the next one. Until one makes it. And
when one takes off? To the moon, Mars, Venus, wherever I make it reach. And then, it’ll be time for
the next one.</p>

<p>Thing is I have no plans on how to fund myself once the limit comes. I don’t really know how my
parents will behave about it. And I just really hope I don’t have to get a job, because my life is
going to be startups.</p>

<p>So, back to YC. I applied, not because of avoidance, but because I get easily distracted when
anxiety comes up. Either way, distraction or not, YC application is almost complete, and I’ll
complete it before the deadline. I’m finishing <a href="https://duckity.com/docs/guides/tune-a-policy">this Duckity guide</a>, and YC did make me think about
lots of things. I’ve been watching some videos of their <a href="https://www.ycombinator.com/library">Startup School</a>, and it has left me
thinking even more.</p>

<p>I’ve been working on Duckity for almost a year and a half now. In that period of time, not all of
it has been productive work (in fact, most of it, until I got a therapist). That was no more than
4 months ago. I built Duckity as I would have built my other apps, which works great yet is missing
the startup side of things.</p>

<p>For example, until about a month ago I knew nothing about marketing. I knew nothing about landing
page copy. I didn’t know cold email outreach was a thing. I didn’t even have a logo, leave alone a
landing page. I’ve been learning a lot about it for the past month or two.</p>

<p>Something that relieves me is that I seem to fit everything they mention they look for in a
startup. Resilience? I’ve promised myself to launch no matter what, and I’m keeping up with it.
How to make money? I have my plans. How to get users? That’s why I’m launching in two days. I’ve
been able to fill out practically every question they asked in their application form, except for
the equity and legal entity part which I haven’t thought of yet because it’s all a one man job up
to now.</p>

<p>I wanna dump some thoughts on it before I eat my fingers off. So basically, I’ve been working on
the product under anxiety for way too long, which means I’ve been detached from the company part of
the company for quite a while, and now I’m starting to remember everything I had thought of when I
was starting out because launch day is coming.</p>

<p>They asked for a pitch of the product, obviously. I don’t have a polished salesy one, so I dumped
what’s in my head. You’ll ask me, “you don’t have a polished pitch two days away from launch?”
Yessir. Thing is the anxiety about the birthday deadline has been eating up part of my mental
bandwidth, and I’m launching anyway because no point in not launching. I have a working product I
can sell.</p>

<p>The pitch I have in the application at the time of writing is this:</p>

<blockquote>
  <p>Duckity protects applications’ login forms, signup forms, and other sensitive forms and actions
without having to ask users to click on buses. Instead of prompting them with puzzles, Duckity
sends over a cryptographic challenge that’s meant to be automatically solved. The challenge is
completely invisible and it’s just math, so it works in any platform without the user having to
do anything. Instead of using hashcash or other probabilistic proof of work algorithms like
competitors do, Duckity uses VDFs, which give you more predictable amounts of work (and therefore
delays) that other algorithms don’t give you, therefore making tuning the protection for UX way
easier. Captchas are usually bad at customization, too. You usually have to trust some black box
AI with “99.9% accuracy guarantees” which in practice are unverifiable, and they give developers
a “confidence score” without much guide on how it’s calculated, so decisions on what to block are
mostly a guess. Duckity is cryptography and waiting to slow down bots and make scaling attacks
costly instead of trying to guess who’s a human and who’s not. VDFs are at the core of Duckity,
then developers can create and customize policies which define not only how hard &amp; slow those
challenges will be to solve, but also at the rates that can be issued and settings that allow a
single attacker trying to pass as many to be correlated across identities. The idea behind that
is that you usually don’t really care if it’s automated automated access or not, rather whether
it’s doing what a normal person would be allowed to do. What’s the issue with an agent sending
messages to someone’s friend on behalf of the human? Not much. It’s an issue when an agent or bot
is used to mass spam people. But it would be a problem if a person did that mass spamming by hand
too, so no point in just saying bots = bad, humans = good. Duckity makes it hard to scale
automated attacks by both using cryptography and by allowing developers to define what allowed
usage looks like. I believe that’ll be more useful as more people transition towards delegating
more work to AI.</p>
</blockquote>

<p>You may be thinking “holy yap”. Yeah, me too. That’s a mix of how the thing works, what it does in
theory (i.e. my original pitch), and the direction I figured out I’d go as I wrote it down.</p>

<p>I don’t use AI agents, yet that last line is quite interesting. I hadn’t thought of it until I was
writing down the pitch. I have been told a bazillion reasons why it won’t work by my parents.
Bazillion falls short, even. But everyone has excuses not to do things. That’s why I’m launching
anyway, and not like those opinions have any weight anyways. My parents aren’t gonna be using
Duckity, they barely even know how it works.</p>

<p>YC guys also mentioned “we’ll see” and “we’ll figure it out” as really nice things for a founder to
have. This person, let’s call them X, is struggling with what career they’ll follow. They have no
clue and their parents are pressuring them a lot. So I told them “let’s start a company”. Yeah, I’m
learning myself, yet we’ll figure it out. I didn’t even know what company to start, I just knew
we’d do something to get moving. They like seals, find them cute, and I’m a programmer, so it had
to be an app. They suggested a seal-related game, I’m down for it, and after a while they suggested
to make a seal restaurant game, something like some other videogames they play. So we got to work,
I told them we’d get something ready to publish in a month or less and set the release date to Nov
14th. We work on it at night once every three days, and we have some stuff working already. It’ll
be ready for that date.</p>

<p>I’m sharing that experience because it’s literally what YC says to like. Even if YC doesn’t pick
me, and I’m planning for them not to, I’m launching Duckity. I’m gonna make it work, and it WILL
work. Launch date is on Tuesday and I’m launching anxiety or not. If you’re some YC guy reading
this, hi! And if you’re not some YC guy reading this, also hi!</p>

<p>Ultimately, everyone has a reason to say no. Actually doing things is what really matters, and
trying and learning everything you can and getting better every second. I have no proof in the
startup world, yet no doubts either. Good luck with whatever you’re building!</p>]]></content><author><name>Nyeki</name></author><summary type="html"><![CDATA[It’s 1:57 AM Sunday morning, I’m launching Duckity on Tuesday. I don’t even know how I got to YC’s site, I was likely geeking around the internet. Thing is, 2 days remained for applications to close, way enough time to fill out an application form, so I went ahead and did it. At the time of writing, I’m only missing to fill up the founder video and the product demo video. I’ll do those either tomorrow or on Monday.]]></summary></entry><entry><title type="html">Welcome to Wlroots! Part 1</title><link href="https://nekidev.github.io/2026/10/30/1-welcome-to-wlroots.html" rel="alternate" type="text/html" title="Welcome to Wlroots! Part 1" /><published>2026-10-30T00:00:00+00:00</published><updated>2026-10-30T00:00:00+00:00</updated><id>https://nekidev.github.io/2026/10/30/1-welcome-to-wlroots</id><content type="html" xml:base="https://nekidev.github.io/2026/10/30/1-welcome-to-wlroots.html"><![CDATA[<p>So I ditched Arch Linux with Hyprland and moved to Ubuntu Desktop with Gnome Shell. People can say
whatever about Ubuntu, in the end it has been a really smooth experience overall. However, I do
have a bunch of complaints.</p>

<ol>
  <li>Some keybinds seem hardcoded into the compositor,</li>
  <li>The settings app’s hotspot doesn’t allow wifi + hotspot even when my network card supports it,</li>
  <li>The settings app’s keybinds aren’t as customizable as hyprland’s without touching the CLI,</li>
  <li>Hyprland breaks. It breaks MANY times. Way too many. Some day they just decided to ditch their
config format and went for Lua. Lua’s great, yet only 1 version to migrate everything before
catastrophe wasn’t nice. And it seems coded for the vibes, which I don’t like.</li>
  <li>Went to the Hyprland Discord server to ask for help with some issue I had, I got greeted with
jokes from the dev and no help at all. Welp.</li>
</ol>

<p>So I set sail to build my own compositor.</p>

<p>For context, I had tried implementing my own wayland compositor with Smithay and Rust before, but
they all just seem to have absolutely zero “newbies click here” step-by-step guide to get to work,
the wlroots docs are just a bunch of method lists, it’s not a nice experience for someone getting
started.</p>

<p>It’s not impossible to get started with it, in fact it’s quite easy. The only issue is the lack of
docs, so here I am. Writing this for myself to learn as I go, and hopefully it becomes useful to
you too.</p>

<p>It’s worth noticing that at the time of writing this I have a compositor accepting connections but
not doing anything useful with them, it just renders a black screen. I have not yet implemented
XDG shell yet, though, so it makes sense stuff hasn’t been rendering.</p>

<p>This is the first post of a bunch I’ll be writing as notes for myself not to forget and better
internalize what I learn. Also notice that I am a beginner to C as well, though I have experience
with Rust, Python, and the dreadful JavaScript.</p>

<p>These posts will also be based on what I learned reading from <a href="https://drewdevault.com/blog/">Drew Devault’s blog</a>. Issue with it
is that many APIs have since changed considerably, so many parts of the code snippets provided in
those examples just don’t work anymore.</p>

<p>Feel free to raise an issue in <a href="https://github.com/Nekidev/nekidev.github.io">this blog’s GitHub issues</a> if you notice any typos or other
mistakes.</p>]]></content><author><name>Nyeki</name></author><summary type="html"><![CDATA[So I ditched Arch Linux with Hyprland and moved to Ubuntu Desktop with Gnome Shell. People can say whatever about Ubuntu, in the end it has been a really smooth experience overall. However, I do have a bunch of complaints.]]></summary></entry><entry><title type="html">How I Got to Launching Duckity</title><link href="https://nekidev.github.io/2026/10/30/2-how-i-got-to-launching-duckity.html" rel="alternate" type="text/html" title="How I Got to Launching Duckity" /><published>2026-10-30T00:00:00+00:00</published><updated>2026-10-30T00:00:00+00:00</updated><id>https://nekidev.github.io/2026/10/30/2-how-i-got-to-launching-duckity</id><content type="html" xml:base="https://nekidev.github.io/2026/10/30/2-how-i-got-to-launching-duckity.html"><![CDATA[<p>I’ll be launching <a href="https://duckity.com/">Duckity</a> next week. In fact, it’s already open for anyone to register but I
barely got any users to try it out.</p>

<p>Duckity begun because I wanted to start a company. I dropped out of university after a year of
systems engineering because I knew I wanted to live my life coding what I like and enjoy, not
working for someone else. I failed every subject at uni because I didn’t even try, so I told my
parents I’d make an app.</p>

<p>I had no clue what app I would make, though, so to get started I started making a cute journaling
app in React Native that was zero-knowledge encrypted. All good, first version was ready, so I
bought my Google Play developer account with some $25 I had gotten for my birthday, uploaded my
app, and went on Payphone to get a bunch of testers. Got em testers, waited the 14 days, and
submitted my app to the play store for review.</p>

<p>The review got rejected. Why, you’d ask me? Thankfully, I had set up analytics in the app as one of
those cool things I was trying out, and it helped. Authentication was made with Logto, which opens
a browser tab to log in, and that wasn’t working for Google Play reviewers, so they just left it
there.</p>

<p>So basically, Logto had to go. Logto was one of those things I was also trying out, because in
practice it was a bit cumbersome to integrate. Things weren’t as smooth as a native ORM model. So I
started building my own auth endpoint. Then I realized I’d need a CAPTCHA or I’d get holy spammed.
It was zero-knowledge encrypted already, which meant I had barely any knowledge of who was who.</p>

<p>Also, I’m an obsessed-with-ux guy. If it doesn’t work perfectly, I change it. And CAPTCHAs were one
of those triggers. reCAPTCHA? Put a webview. hCaptcha? They’re there for Discord, and it’s hell to
use there. Cloudflare? Same as reCAPTCHA. There was nothing there that could just be invisible and
smooth on UX. I also tried Altcha, but it solved challenges on my phone in, hear me out, from 1
second to 35 seconds. That’s their landing page demo, so I assume they were nice on me. I didn’t
want to hear about them again.</p>

<p>I went ahead and asked ChatGPT “hey, how do I make my own CAPTCHA” so it told me that there’s
either PoW and all the browser-based ones. I liked the PoW idea, so I started playing with some
hashing algorithms and hashcash. It worked, it was really easy to make, it was native. But it
varied wildly in solving times. Add a bit? The time didn’t grow up linearly, more like
exponentially. So how am I even gonna tune something like that?</p>

<p>I played with SHA-256, Argon (bad idea), Blake. None was good. SHA-256 showed the UX variety which
was bad. Argon means it’s dead easy to overwhelm the validation server. Blake somehow never solved
because the first bits weren’t perfectly random. So I asked ChatGPT if there’s anything like PoW
without those drawbacks. And after a few tries, it mentioned VDFs.</p>

<p>VDFs, for context, are a bunch of recursive math functions that are sequential. I.e., you can’t use
two CPU cores to go faster. That means an attacker with OpenAI’s GPU farm won’t solve VDFs in a
nanosecond while you in your Galaxy S1 take 4 business days for it to solve. It’s pretty much a
loop of N iterations where each iteration depends on the previous one, so you can’t just go ahead
and skip some steps or try to parallelize it. It’s all gonna depend on your CPU speed. Those N
iterations are the “difficulty/hardness” of a VDF “challenge”. I.e. what Duckity tweaks with your
policies.</p>

<p>I initially went for Sloth. It’s simple and works. It did its job. The whole original
implementation was on Sloth. I made the control plane in FastAPI with Tortoise ORM (I have a
<a href="https://github.com/nekidev/uv-fastapi-tortoise">template on GitHub</a> if you happen to be curious) and the data plane with Rust and Axum. I had
originally built it on <a href="https://docs.rs/turbostore">TurboStore</a>, which is great for that stuff. Cache cleanup did not scale
well at all, but it is hella fast. Then I wanted to make it distributed while adding a caching
layer directly to the database, so I started working on the design.</p>

<p>After a while of designing it and working on implementing it, I realized that Redis was gonna be
the quicker development choice, so I put TurboStore on hold and migrated to Redis. Actually, I
rewrote the data plane at least 3 times, which again, not good at all. That’s one of the many
lessons I’ve learned.</p>

<p>I also later migrated to Wesolowski VDF after hearing something about side channel attacks on Sloth
VDF, so to be sure and not having to migrate later on, I went ahead and made it Wesolowski VDFs
instead.</p>

<p>Remember how I originally needed <a href="https://duckity.com/">Duckity</a> for a React Native app? Well, I haven’t yet made one,
because I haven’t been working on that app ever since. I’ve told myself I’m launching that app,
though, it’s just paused, so I’ll write that React Native SDK sooner than later.</p>

<p>Well, fast forward to about a month ago, I had to make a landing page and I had no clue how to make
one that sells. I learned a few things by asking ChatGPT:</p>

<ul>
  <li>It has to be customer-outcome oriented. The customer wants to know how it serves them, not really
how other than to know you’re not a faker.</li>
  <li>That’s it.</li>
</ul>

<p>But whatever, I’ll learn once it’s up. So I set up Umami and started sending cold emails. At first
I went to publicapis.dev and started going one by one checking out if I could get emails to send to
from their websites and keeping track of them in a Google Sheets doc. First day I sent 4 emails in
about 4 hrs, because I was learning how to write the cold emails properly. Day 2 I sent like 8.
Yet, that was being wayy too slow. It was hours and hours of work for a few emails, so I found out
<a href="https://hunter.io/">Hunter</a> God knows how.</p>

<p>Hunter’s a great platform. It has a free tier, and I used up all of those 50 credits in my first
month. For context, a credit is more or less equal to one email you get to cold email, so that was
about 50 emails. I kept sending cold emails by hand, which was taking me too much time, getting me
no replies, and I couldn’t use that time on something else. So I checked out their sequences tool
to make email templates and send them out, but I had 3 issues:</p>

<ol>
  <li>Their templating is extremely basic, you can’t do more than basic substring replacement. It
makes those emails smell like autogenerated from kilometers away, and people like to be talked
to by humans, not robots.</li>
  <li>Their AI reviewer was not reliable at all. I wrote an email, it said “great! not spammy, sounds
human,” etc. I changed one word and it went “nah, hella spammy, nobody’s gonna read this.”
Someone who knows more than I do about marketing may not find this issue big, but as a thing I
was relying on, not good.</li>
  <li>Time to send those emails. I had read that company emails (i.e. @duckity.com) get better reply
rates than free personal ones (i.e. @gmail.com). And Hunter required a paid subscription to send
emails via SMTP.</li>
</ol>

<p>So I had an issue. I also had issues with my emails reaching the spam folder with my custom SMTP
server, so I decided I’d have a better time having some established provider host my email. And
<a href="https://mail.zoho.com/">Zoho Mail</a> was quite good! Their free tier gives me up to 5 email addresses for my duckity.com
domain, so I set that up. SMTP is only available on paid plans, but Hunter doesn’t integrate with
SMTP for free anyway, so not a big loss.</p>

<p>So Hunter wasn’t gonna work for the email sending thing, and I had my Zoho Mail set up. I went
ahead and built my own little cold email app with FastAPI and Jinja! It lets me do everything I
needed Hunter to do, like scheduling, and it imports my leads from Hunter. Plus, email templating
is with Jinja and I made it so that I can add custom fields to leads, so I can personalize cold
emails even more. The thing had:</p>

<ul>
  <li>Sequences with many steps,</li>
  <li>An email scheduler,</li>
  <li>Email frequency limits,</li>
  <li>Automatic email sending via the Zoho Mail API,</li>
  <li>Listening for incoming emails and stopping sequences for a lead on reply,</li>
  <li>Jinja email templating (with a basic highlighted editor),</li>
  <li>Custom fields to label leads,</li>
  <li>Basic analytics,</li>
  <li>A conversation viewer in-app</li>
</ul>

<p>The UI was very basic HTML, CSS just enough to place stuff where it should go. It didn’t even have
multi-user support because it was just for me. I wrote my emails, I left it running for some days,
and once all emails were sent, I had no replies. Days passed, no replies.</p>

<p>I told myself that after ~50 emails sent, no replies was little evidence, but I had a problem. I
didn’t know of a reliable way to scale it up. Today I think I could go through GitHub and checking
out those people who have public emails, but by now I have left aside the cold email strategy for a
while.</p>

<p>I got v0 to make me the landing page, and I liked the style. Made some tweaks with v0 over the
course of some days, yet at the end of it all, it was obviously AI-generated. Google refused to
index it. And the copy was so generic. So I ditched it.</p>

<p>That v0 landing page was after another landing page I had made, but I was getting anxious so I
wasn’t being able to finish it. And it was looking too playful so I worried it wouldn’t get me any
serious customers. Anyway, with that hand-made landing page I joined a lot of programming Discord
servers and posted it in every showcase channel I found. Yeah, it’s not open source, yet I broke
some rules for the sake of it.</p>

<p>And it worked. I got 3 users to sign up, which was great news. And even more, I had ended up
restraining from posting in every server because I was worried I’d fuck up my opportunities so I
didn’t use them. Bad mistake too.</p>

<p>I never heard back from those 3 users, but at least I got them to sign up, which is great. It means
that worked. I also DMed one guy making an anime API, and I made a few mistakes like claiming they
don’t use a CAPTCHA when they do, but only in their signup form while I only checked their login
form. Mistakes and all, they replied with a decently-long message.</p>

<p>The message was great. They said they wouldn’t try Duckity out, but they told me <em>why</em>, which is
great.</p>

<ul>
  <li>They didn’t know whether Duckity actually works,</li>
  <li>They didn’t want to have to add me as a processor and update their privacy policy,</li>
  <li>They didn’t know what data I collect or process,</li>
  <li>They didn’t know how I collect and process data.</li>
</ul>

<p>All useful information, so I added all that info as FAQs to the landing page. In the end they
didn’t sign up, but I got all of that useful information.</p>

<p>Fast forward to 2 weeks ago, I started working on a new landing page after ditching the
AI-generated one. I wanted to be really creative, after all Duckity’s branding is meant to get out
of that serious security corporation vibe and be more approachable and friendly. So I made it
slides-like, with Rubik and Rubik Glitch fonts, with a bright yellow background, and cartoonish
buttons.</p>

<p>God knows whether it’ll sell, yet it’s done and works. I finished it some days ago. It’s now
indexed on Google, unlike the AI-generated one. Now that it’s done and launch is coming soon (next
Tuesday), no point in getting anxious over it.</p>

<p>Anxiety was a big, big thing during these almost 2 years of working on launching a company. Mind
blocks, freezes, stress 24/7. Now I’m getting to the launch date, and only because I read
everywhere that you have to launch ASAP. I was trying to get something done perfectly, but that
just won’t get me anywhere since there’ll always be something to do. I cut off everything that was
pending from my project manager tasks list, and worked to get it launched soon.</p>

<p>Right now, I’m finishing a guide on how to tune policies. It’s Friday, I have today and tomorrow to
work on Duckity, then I’ll use Sunday to keep playing with wayland and wlroots (check my previous
blog post), then Monday to work on Duckity and polish all I need for launch on Tuesday. I’ll be
launching on Hacker News, Product Hunt, Indie Hacker, and Reddit to begin with. Maybe TinyLaunch
too. I’m also gonna need to start blogging to work on SEO.</p>

<p>Anxiety. I’ve said it already, it’s a big one. Throughout these almost 2 years, anxiety has been on
me most of the time. Yet, I know I’m not giving up, so I’m still figuring out how to handle it. My
main complaint about it is how it makes it harder to learn when it’s all about learning. And it has
slowed down my progress many, many times. But hey, never giving up and never stopping.</p>

<p>I live with my parents, and they’re not making it easy either at all. They’ve insulted me
consistently, cut my wifi so I can’t work at home nor out of the schedules they want me to, have
told me all the ways they think it won’t work, how they see everyone fail, not nice. But welp, this
company will take me out of here, and if it’s not this one, the next one will be. Or the next one.
First thing I’ll buy once company costs are covered is some big fat juicy hamburger.</p>

<p>Welp, whatever. Back to coding. I feel like I should be writing a conclusion but I don’t have
anything to say because I drifted when anxiety hit. Good luck with whatever you’re building!</p>]]></content><author><name>Nyeki</name></author><summary type="html"><![CDATA[I’ll be launching Duckity next week. In fact, it’s already open for anyone to register but I barely got any users to try it out.]]></summary></entry></feed>