aboutsummaryrefslogtreecommitdiff
path: root/public/trying-to-build-a-new-kind-of-terminal-emulator.html
blob: 77350cbd863c7d0c0a0e6bcde52daf8eb6c5840b (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
<!doctype html><html lang=en-us><meta charset=utf-8><meta name=viewport content="width=device-width,initial-scale=1"><meta name=generator content="JBMAFP - github.com/mitjafelicijan/jbmafp"><link href="data:image/x-icon;base64,AAABAAEAEBAAAAEAIABoBAAAFgAAACgAAAAQAAAAIAAAAAEAIAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAL69vf8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAv76+/8LBwQkAAAAAAAAAAAAAAAC+vb3/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAL+9vf/Bv78JAAAAAAAAAAAAAAAAu7q6/wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAC7ubr/vr29CAAAAAAAAAAAy8nJAZ6foP8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAnqGj/6GipAoAAAAAHLjU/xcXHf/BwsL/I8XY/yPK3v8XGiD/IbjL/yPF2f8XGiD/Fxkf/yLF2f8gnK3/Fxog/62ztv8fwNf/FRcd/x271v8mz93/GRsi/xkXHf8p097/GiIp/xobIv8p0t3/KdPe/xocIv8fYmr/KNPe/xoZH/8aHCL/J87c/xy81/8VFxz/IsPZ/8zS0/8XGiD/Ir/R/yPH2/8XGiD/Fxkf/yPH2/8dd4T/GBog/yPJ3f8jyNr/uru9/xcUGv8cudb/EhITDKi5vRKlvMP/RUpOERwcHRAdOj4QHTk8EBwdHRAdNTgQHTo/EBwcHRAcHB0QSGduEKW4vf+koqQfHzg+EBqz0ewSFRv7EyMr/xq51vsTERb7ExUb+xq41fsau9j7ExUb+xiPp/sZudb7ExUb+xMVG/sZuNX/GKvI/BIUGfMdvdn/IrfL/xcaIP8n1eb/J9Dh/xkcIf8ZGR7/J8/f/xxCSv8ZGyH/J9Dg/ybQ4P8ZHCL/FSQs/yPK3/8UExj/GE1b/ybS5P8ZGB7/Ghwj/ynW5P8p2Ob/Ghwi/yWrtv8p1eH/Ghwi/xocIv8p1uT/J8XT/xkcIv8m1un/Hb7d/xUYH/8hzOr/HtHu/xcaIf8XGB//I8vi/xgxOv8XGSD/I8rg/yPK4P8XGiD/GUFL/yPP6f8SERj/Fhkh/x3A4f8AAAAAJ2f9/ydr//8mZPH/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAlYu38J2v//ydo/f8AAAAAAAAAAAd8/fkFqf//Iob8sAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMY39awWr//8FfP3/AAAAAAAAAAAFm/7/SfD//wR+/f8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOB/f9B7v//BaX+/wAAAAAAAAAAQ878SAyZ/v9n1v4KAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADu9v8DDJb+/z3N/XgAAAAA3/sAAN/7AADf+wAA3/sAAAAAAAAAAAAAAAAAAN/7AAAAAAAAAAAAAAAAAAAAAAAAj/EAAI/5AACP8QAA3/sAAA==" rel=icon type=image/x-icon><title>Trying to build a New kind of terminal emulator for the modern age</title><meta name=description content="Over the past few weeks, I have been really thinking about terminal emulators,how we interact with computers, the separation of text-based programs and GUIones."><meta name=author content="Mitja Felicijan"><link rel=alternate type=application/rss+xml title="Mitja Felicijan's posts" href=https://mitjafelicijan.com/index.xml><link rel=alternate type=application/rss+xml title="Mitja Felicijan's notes" href=https://mitjafelicijan.com/notes.xml><style>:root{--border-color:gainsboro;--border-size:2px;--link-color:blue;--bg-color:#eee}*::selection{background:var(--link-color);color:#fff}*::-moz-selection{background:var(--link-color);color:#fff}*::-webkit-selection{background:var(--link-color);color:#fff}body{padding:2.5rem;max-width:1900px;background:#fff;font-family:sans-serif;line-height:1.35rem;font-size:16px}hr{border:0;border-bottom:var(--border-size)solid var(--border-color);margin-block-start:1.5rem}a{color:var(--link-color);text-decoration:none}h1,h2,h3{line-height:initial}h1{font-size:xx-large}footer{margin-block-start:2rem}cap{text-transform:capitalize}blockquote{font-style:italic}table{max-width:100%;border:var(--border-size)solid var(--border-color);border-collapse:separate;border-spacing:0}table thead tr th{border-bottom:var(--border-size)solid var(--border-color);text-align:left}table th,table td{padding:.5em .8em}ul.list li{padding:.2em 0}ul{line-height:1.35em}pre{text-wrap:nowrap;overflow-x:auto;padding:0 1em;border:var(--border-size)solid var(--border-color)}code{padding:0 3px;font-size:14px;border:0;background:var(--bg-color)}pre code{line-height:1.3em;background:#fff}pre,code,pre *,code *{font-family:monospace}figure{margin-inline-start:0;margin-inline-end:0}figure img{}figcaption{text-align:center}figcaption p{margin:.3em 0 0}img,video,audio{width:800px;max-width:100%;border:var(--border-size)solid var(--border-color);padding:.5em}header nav{display:flex;gap:.9rem}article iframe{margin:0!important}audio::-webkit-media-controls-enclosure{border-radius:0}@media only screen and (max-width:600px){body{padding:.5em;word-wrap:break-word}header nav{gap:.7rem}header nav .hob{display:none}a{word-wrap:break-word}}</style><header><nav class=main itemscope itemtype=http://schema.org/SiteNavigationElement role=toolbar><a href=/>Home</a>
<a href=/#posts>Posts</a>
<a href=/#notes>Notes</a>
<a href=/#sideprojects class=hob>Side Projects</a>
<a href=/vault.html>Vault</a>
<a href=https://github.com/mitjafelicijan target=_blank>Code</a>
<a href=/mitjafelicijan.pgp.pub.txt target=_blank class=hob>PGP</a>
<a href=/curriculum-vitae.html>CV</a>
<a href=/index.xml target=_blank class=hob>RSS</a></nav></header><main role=main><article itemtype=http://schema.org/Article><h1 itemtype=headline>Trying to build a New kind of terminal emulator for the modern age</h1><p><cap>post</cap>, Jan 26, 2023 on <a href=https://mitjafelicijan.com>Mitja Felicijan's blog</a><div><p>Over the past few weeks, I have been really thinking about terminal emulators,
how we interact with computers, the separation of text-based programs and GUI
ones. To be perfectly honest, I got pissed off one evening when I was cleaning
up files on my computer. Normally, I go into console and do <code>ncdu</code> and check
where the junk is. Then I start deleting stuff. Without any discrimination,
usually. But when it comes to screenshots, I have learned that it's good to keep
them somewhere near if I need to refer to something that I was doing. I am an
avid screenshot taker. So at that point I checked Pictures folder and also did a
basic search <code>find . -type f -name "*.jpg"</code> for all the JPEG files in my home
directory and immediately got pissed off. Why can’t I see thumbnails in my
terminal? I know why, but why in the year of 2022 this is still a problem. I am
used to traversing my disk via terminal. I am faster, and I am more comfortable
this way. But when it comes to visualization, I then need to revert to GUI
applications and again find the same file to see it. I know that programs like
<code>feh</code> and <code>sxiv</code> are available, but I would just like to see the preview. Like
<a href=https://jupyter.org/>Jupyter notebook</a> or something similar. Just having it
inline. Part of a result.<p>It also didn’t help that I was spending some time with the <a href=https://plan9.io/plan9/>Plan
9</a> Operating system. More specifically
<a href=http://9front.org/>9FRONT</a>. The way that <a href=http://acme.cat-v.org/>ACME editor</a>
handles text editing is just wonderful. Different and fresh somehow, even though
it’s super old.<p>So, I went on a lookout for an interesting way of visualizing results of some
query. I found these applications to be outstanding examples of how not to be a
captive of a predetermined way of doing things.<ul><li><a href=https://www.wolfram.com/mathematica/>Wolfram Mathematica</a><li><a href=https://jupyter.org/>Jupyter notebooks</a><li><a href=http://www.9front.org>Plan 9 / 9FRONT</a><li><a href=https://templeos.org/>Temple OS</a><li><a href=https://www.gnu.org/software/emacs/>Emacs</a></ul><p>My idea is not as out there as ACME is, but it is a spin on the terminal
emulators. I like the modes that Vi/Vim provides you with. I like the way the
Emacs does its own <code>M-x</code> <code>M-c</code>. Furthermore, I really like how Mathematica and
Jupyter present the data in a free flowing form. And I love how Temple OS is
basically a C interpreter on some level.<blockquote><p><strong>Note:</strong> This is part 1 of the journey. Nowhere finished yet. I am just
tinkering with this at the moment. This whole thing can easily spectacularly
fail.</blockquote><p>So I started. I knew that I wanted to have the couple of modes, but I didn’t
like the repetition of keystrokes, so the only option was to have some sort of
toggle and indicate to the user that they are in a special mode. Like Vi does
for Normal and Visual mode.<p>These modes would for the first version be:<ul><li><em>Preview mode</em> (toggle with Ctrl + P)<ul><li>When this mode would be enabled, the <code>ls</code> command would try to find images
from the results and display thumbnails from them in the terminal itself.
No ASCII art. Proper images. In a grid!</ul><li><em>Detach mode</em> (toggle with Ctrl + D)<ul><li>When this mode would be enabled, every command would open a new window
and execute that command in it. This would be useful for starting <code>htop</code>
in a separate window.</ul></ul><p>The reason for having these modes togglable is to not ask for previews every
time. You enable a mode and until you disable it, it behaves that way. Purely
out of ergonomic reasons.<p>I would like to treat every terminal I open as a session mentally. When I start
using the terminal, I start digging deeper into the issue I am trying to
resolve. And while I am doing this, I would like to open detached windows
etc. A lot of these things can be done easily with something like
<a href=https://i3wm.org/>i3</a>, but also that pull you out of the context of what you
were doing. I would like to orchestrate everything from one single point.<p>In planning for this project, I knew that I would need to use a language like C
and a library such as <a href=https://www.libsdl.org/>SDL2</a> in order to achieve the
desired results. I had considered other options, but ultimately determined that
<a href=https://www.libsdl.org/>SDL2</a> was the best fit based on its capabilities and
reputation in the programming community.<p>At first, I thought the idea of a hardware accelerated terminal was a bit of a
joke. It seemed like such a niche and unnecessary feature, especially given the
fact that terminal emulators have been around for decades and have always relied
on software rendering. But to be fair, <a href=https://alacritty.org/>Alacritty</a> is
doing the same thing. Well, they are doing a remarkable job at it.<p>So, I embarked on a journey. Everything has to start somewhere. For me, it
started with creating a window! It has to start somewhere. 🙂<pre tabindex=0 style=background-color:#fff><code><span style=display:flex><span><span style=color:green>// Oh, Hi Mark!
</span></span></span><span style=display:flex><span><span style=color:green>// Create the window, obviously.
</span></span></span><span style=display:flex><span><span style=color:green></span>SDL_Window *window = SDL_CreateWindow(
</span></span><span style=display:flex><span>  WINDOW_TITLE, SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED,
</span></span><span style=display:flex><span>  WINDOW_WIDTH, WINDOW_HEIGHT,
</span></span><span style=display:flex><span>  SDL_WINDOW_RESIZABLE | SDL_WINDOW_OPENGL | SDL_WINDOW_SHOWN);
</span></span></code></pre><p>I continued like this to get some text displayed on the screen.<p>I noted that
<a href=https://wiki.libsdl.org/SDL_ttf/TTF_RenderText_Solid><code>TTF_RenderText_Solid</code></a>
rendered text really poorly. There were no antialiasing at all. In my wisdom, I
never checked the documentation. Well, that was a fail. To uneducated like me:
<code>TTF_RenderText_Solid</code> renders Latin1 text at fast quality to a new 8-bit
surface. So, that's why the texts looked like shit. No wonder.<p>Remarks on <code>TTF_RenderText_Solid</code>: This function will allocate a new 8-bit,
palettized surface. The surface's 0 pixel will be the colorkey, giving a
transparent background. The 1 pixel will be set to the text color.<p>After I replaced it with
<a href=https://wiki.libsdl.org/SDL_ttf/TTF_RenderText_LCD><code>TTF_RenderText_LCD</code></a> which
renders Latin1 text at LCD subpixel quality to a new ARGB surface, the text
started looking good. Really make sure you read the documentation. It’s actually
good. As a side note, you can find all the documentation regarding <a href=https://wiki.libsdl.org/>SDL2 on
their Wiki</a>.<p>After that was done, I started working on displaying other things like <code>Preview</code>
and <code>Detach</code> modes. This wasn’t really that hard. In SDL2 you can check all the
available events with <code>while (SDL_PollEvent(&amp;event) > 0)</code> and have a bunch of
switch statements to determine which key is currently being pressed. More about
keys, <a href=https://documentation.help/SDL/sdlkey.html>SDLKey</a> and mroe about
pooling the events on
<a href=https://documentation.help/SDL/sdlpollevent.html>SDL_PollEvent</a>.<pre tabindex=0 style=background-color:#fff><code><span style=display:flex><span><span style=color:#00f>while</span> (SDL_PollEvent(&amp;event) &gt; 0)
</span></span><span style=display:flex><span>{
</span></span><span style=display:flex><span>  <span style=color:#00f>switch</span> (event.type)
</span></span><span style=display:flex><span>  {
</span></span><span style=display:flex><span>  <span style=color:#00f>case</span> SDL_QUIT:
</span></span><span style=display:flex><span>    running = false;
</span></span><span style=display:flex><span>    <span style=color:#00f>break</span>;
</span></span><span style=display:flex><span>
</span></span><span style=display:flex><span>  <span style=color:#00f>case</span> SDL_TEXTINPUT:
</span></span><span style=display:flex><span>    <span style=color:#00f>if</span> (!meta_key_pressed)
</span></span><span style=display:flex><span>    {
</span></span><span style=display:flex><span>      strncat(input_prompt_text, event.text.text, 1);
</span></span><span style=display:flex><span>      update_input_prompt = true;
</span></span><span style=display:flex><span>    }
</span></span><span style=display:flex><span>    <span style=color:#00f>break</span>;
</span></span><span style=display:flex><span>	}
</span></span><span style=display:flex><span>}
</span></span></code></pre><p>After that was somewhat working correctly, I started creating a struct that
would hold all the commands and results and I call them Cells. Yes, I stole that
naming idea from Jupyter.<pre tabindex=0 style=background-color:#fff><code><span style=display:flex><span><span style=color:#00f>typedef</span> <span style=color:#00f>struct</span>
</span></span><span style=display:flex><span>{
</span></span><span style=display:flex><span>  <span style=color:#2b91af>char</span> *command;
</span></span><span style=display:flex><span>  <span style=color:#2b91af>char</span> *result;
</span></span><span style=display:flex><span>  SDL_Surface *surface;
</span></span><span style=display:flex><span>  SDL_Texture *texture;
</span></span><span style=display:flex><span>  SDL_Rect rect;
</span></span><span style=display:flex><span>} Cell;
</span></span></code></pre><p>I am at a place now where I am starting to implement scrolling. This will for
sure be fun to code. Memory management in C is super easy. 😂<p>I have also added a simple <a href=https://en.wikipedia.org/wiki/INI_file>INI file like
configuration</a> support. It is done in an
<a href=https://github.com/nothings/stb/blob/master/docs/stb_howto.txt>STB style of
header</a> and maps
to specific options supported by the terminal. It is not universal, and the code
below demonstrates how I will use it in the future.<pre tabindex=0 style=background-color:#fff><code><span style=display:flex><span><span style=color:#00f>#ifndef CONFIG_H
</span></span></span><span style=display:flex><span><span style=color:#00f>#define CONFIG_H
</span></span></span><span style=display:flex><span><span style=color:#00f></span>
</span></span><span style=display:flex><span><span style=color:green>/*
</span></span></span><span style=display:flex><span><span style=color:green># This is a comment
</span></span></span><span style=display:flex><span><span style=color:green>
</span></span></span><span style=display:flex><span><span style=color:green># This is the first configuration option
</span></span></span><span style=display:flex><span><span style=color:green>dettach=value11111
</span></span></span><span style=display:flex><span><span style=color:green>
</span></span></span><span style=display:flex><span><span style=color:green># This is the second configuration option
</span></span></span><span style=display:flex><span><span style=color:green>preview=value22222
</span></span></span><span style=display:flex><span><span style=color:green>
</span></span></span><span style=display:flex><span><span style=color:green># This is the third configuration option
</span></span></span><span style=display:flex><span><span style=color:green>debug=value33333
</span></span></span><span style=display:flex><span><span style=color:green>*/</span>
</span></span><span style=display:flex><span>
</span></span><span style=display:flex><span><span style=color:green>// Define a struct to hold the configuration options
</span></span></span><span style=display:flex><span><span style=color:green></span><span style=color:#00f>typedef</span> <span style=color:#00f>struct</span>
</span></span><span style=display:flex><span>{
</span></span><span style=display:flex><span>    <span style=color:#2b91af>char</span> dettach[256];
</span></span><span style=display:flex><span>    <span style=color:#2b91af>char</span> preview[256];
</span></span><span style=display:flex><span>    <span style=color:#2b91af>char</span> debug[256];
</span></span><span style=display:flex><span>} Config;
</span></span><span style=display:flex><span>
</span></span><span style=display:flex><span><span style=color:green>// Read the configuration file and return the options as a struct
</span></span></span><span style=display:flex><span><span style=color:green></span><span style=color:#00f>extern</span> Config read_config_file(<span style=color:#00f>const</span> <span style=color:#2b91af>char</span> *filename)
</span></span><span style=display:flex><span>{
</span></span><span style=display:flex><span>  <span style=color:green>// Create a struct to hold the configuration options
</span></span></span><span style=display:flex><span><span style=color:green></span>  Config config = {0};
</span></span><span style=display:flex><span>
</span></span><span style=display:flex><span>  <span style=color:green>// Open the configuration file
</span></span></span><span style=display:flex><span><span style=color:green></span>  FILE *file = fopen(filename, <span style=color:#a31515>&#34;r&#34;</span>);
</span></span><span style=display:flex><span>
</span></span><span style=display:flex><span>  <span style=color:green>// Read each line from the file
</span></span></span><span style=display:flex><span><span style=color:green></span>  <span style=color:#2b91af>char</span> line[256];
</span></span><span style=display:flex><span>  <span style=color:#00f>while</span> (fgets(line, <span style=color:#00f>sizeof</span>(line), file))
</span></span><span style=display:flex><span>  {
</span></span><span style=display:flex><span>    <span style=color:green>// Check if this line is a comment or empty
</span></span></span><span style=display:flex><span><span style=color:green></span>    <span style=color:#00f>if</span> (line[0] == <span style=color:#a31515>&#39;#&#39;</span> || line[0] == <span style=color:#a31515>&#39;\n&#39;</span>)
</span></span><span style=display:flex><span>      <span style=color:#00f>continue</span>;
</span></span><span style=display:flex><span>
</span></span><span style=display:flex><span>    <span style=color:green>// Parse the line to get the option and value
</span></span></span><span style=display:flex><span><span style=color:green></span>    <span style=color:#2b91af>char</span> option[128], value[128];
</span></span><span style=display:flex><span>    <span style=color:#00f>if</span> (sscanf(line, <span style=color:#a31515>&#34;%[^=]=%s&#34;</span>, option, value) != 2)
</span></span><span style=display:flex><span>      <span style=color:#00f>continue</span>;
</span></span><span style=display:flex><span>
</span></span><span style=display:flex><span>    <span style=color:green>// Set the value of the appropriate option in the config struct
</span></span></span><span style=display:flex><span><span style=color:green></span>    <span style=color:#00f>if</span> (strcmp(option, <span style=color:#a31515>&#34;dettach&#34;</span>) == 0)
</span></span><span style=display:flex><span>    {
</span></span><span style=display:flex><span>      strncpy(config.option1, value, <span style=color:#00f>sizeof</span>(config.option1));
</span></span><span style=display:flex><span>    }
</span></span><span style=display:flex><span>    <span style=color:#00f>else</span> <span style=color:#00f>if</span> (strcmp(option, <span style=color:#a31515>&#34;preview&#34;</span>) == 0)
</span></span><span style=display:flex><span>    {
</span></span><span style=display:flex><span>      strncpy(config.option2, value, <span style=color:#00f>sizeof</span>(config.option2));
</span></span><span style=display:flex><span>    }
</span></span><span style=display:flex><span>    <span style=color:#00f>else</span> <span style=color:#00f>if</span> (strcmp(option, <span style=color:#a31515>&#34;debug&#34;</span>) == 0)
</span></span><span style=display:flex><span>    {
</span></span><span style=display:flex><span>      strncpy(config.option3, value, <span style=color:#00f>sizeof</span>(config.option3));
</span></span><span style=display:flex><span>    }
</span></span><span style=display:flex><span>  }
</span></span><span style=display:flex><span>
</span></span><span style=display:flex><span>  <span style=color:green>// Close the configuration file
</span></span></span><span style=display:flex><span><span style=color:green></span>  fclose(file);
</span></span><span style=display:flex><span>
</span></span><span style=display:flex><span>  <span style=color:green>// Return the configuration options
</span></span></span><span style=display:flex><span><span style=color:green></span>  <span style=color:#00f>return</span> config;
</span></span><span style=display:flex><span>}
</span></span><span style=display:flex><span>
</span></span><span style=display:flex><span><span style=color:#00f>#endif
</span></span></span></code></pre><p>This is as far as I managed to get for now. I have a daily job and this
prohibits me to work on these things full time. But I should probably get back
and finish this. At least have a simple version working out, so I can start
testing it on my machines. Fingers crossed. 🕵️‍♂️</div></article></main><section><hr><h2>Posts from blogs I follow around the net</h2><ul><li><a href=https://utcc.utoronto.ca/~cks/space/blog/linux/NFSv4ServerLockClients target=_blank rel=noopener>Finding which NFSv4 client owns a lock on a Linux NFS(v4) server</a><a href=https://utcc.utoronto.ca/~cks/space/blog/>Chris's Wiki :: blog</a><div>A while back I wrote an entry about finding which NFS client owns
a lock on a Linux NFS server, which turned
out to be specific to NFS v3 (which I really should have seen coming,
since it involved NLM and lockd). Finding the NFS v4 client that
owns a lock is, depending on your perspective, either simpl…<li><a href=http://www.landley.net/notes-2023.html#28-10-2023 target=_blank rel=noopener>October 28, 2023</a><a href=http://www.landley.net/notes-2023.html>Rob Landley's Blog Thing for 2023</a><div>Oh good grief, two of my least favorite licensing people, Larry Rosen
and Bradley Kuhn, are interacting on the OSI's license-discuss
list where the're doing
bad computer history and insisting that a guy Larry Rosen
coincidentally interviewed for a book years ago is clearly the origin of
somethin…<li><a href="http://offbeatpursuit.com:80/blog/?id=25" target=_blank rel=noopener>A fix by any other name</a><a href=http://offbeatpursuit.com:80/blog/>WLOG - blog</a><div>tags:
i2c, plan9
Another month, another file system.
Well, if you can’t fix it in software, fix it in hardware (looking at
you, bme680, we’re not
done yet). The show must go on, as they say, and I would like my
experiments to go on.
So a “new” addition to the environmental sensor family connected to
the h…<li><a href=https://mirzapandzo.com/next-image-url-parameter-is-valid-but-upstream-response-is-invalid target=_blank rel=noopener>Next/Image "url" parameter is valid but upstream response is invalid</a><a href=https://mirzapandzo.com/>Mirza Pandzo's Blog</a><div>Getting "url" parameter is valid but upstream response is invalid error with Next/Image on WSL2<li><a href=https://drewdevault.com/2023/10/13/Going-off-script.html target=_blank rel=noopener>Going off-script</a><a href=https://drewdevault.com>Drew DeVault's blog</a><div>There is a phenomenon in society which I find quite bizarre. Upon our entry to
this mortal coil, we are endowed with self-awareness, agency, and free will.
Each of the 8 billion members of this human race represents a unique person, a
unique worldview, and a unique agency. Yet, many of us have the sam…<li><a href=https://szymonkaliski.com/writing/2023-10-02-building-a-diy-pen-plotter/ target=_blank rel=noopener>Building a DIY Pen Plotter</a><a href=http://github.com/dylang/node-rss>Szymon Kaliski</a><div>This article documents my learnings from designing and building a DIY Pen Plotter during the summer of 2023.
My ultimate goal is to build my…<li><a href=https://neil.computer/notes/chart-of-accounts-for-startups-and-saas-companies/ target=_blank rel=noopener>Chart of Accounts for Startups and SaaS Companies</a><a href=https://neil.computer/>Neil Panchal</a><div>Accounting is fundamental to starting a business. You need to have a basic understanding of accounting principles and essential bookkeeping. I had to learn it. There was no choice. For filing taxes, your CPA is going to ask you for an Income Statement (also known as P/L statement). If<li><a href=https://journal.valeriansaliou.name/deploy-a-nomad-cluster-on-alpine-linux-with-vultr/ target=_blank rel=noopener>Deploy a Nomad Cluster on Alpine Linux with Vultr</a><a href=https://journal.valeriansaliou.name/>Valerian Saliou</a><div>After spending countless hours trying to understand how to deploy my apps on Kubernetes for the first time to host Mirage, an AI API service that I run, I ended up making myself a promise that the next app I work on would be using a more productive & simpler<li><a href=https://jcs.org/2023/10/25/wifi_da target=_blank rel=noopener>BlueSCSI Wi-Fi Desk Accessory 1.0 Released</a><a href=https://jcs.org/>joshua stein</a><div>BlueSCSI Wi-Fi Desk Accessory
1.0 has been released:
wifi_da-1.0.sit
(StuffIt 3 archive)
SHA256: ccfc9d27dd5da7412d10cef73b81119a1fec3848e4d1d88ff652a07ffdc6a69aSHA1: ff124972f202ceda6d7fa4788110a67ccda6a13a
This is the initial public release of my BlueSCSI Wi-Fi Desk Accessory for
classic MacOS.<li><a href=https://michael.stapelberg.ch/posts/2023-10-25-my-all-flash-zfs-network-storage-build/ target=_blank rel=noopener>My 2023 all-flash ZFS NAS (Network Storage) build</a><a href=https://michael.stapelberg.ch/>Michael Stapelbergs Website</a><div>For over 10 years now, I run two self-built NAS (Network Storage) devices which serve media (currently via Jellyfin) and run daily backups of all my PCs and servers.
In this article, I describe my goals, which hardware I picked for my new build (and why) and how I set it up.
Design Goals
I use my netw…</ul><p>Generated with <a href=https://git.sr.ht/~sircmpwn/openring target=_blank rel=noopener>openring</a>.</section><footer><hr><p><big><strong>Want to comment or have something to add?</strong></big><p>You can write me an email
at <a href=mailto:mitja.felicijan@gmail.com>mitja.felicijan@gmail.com</a> or
catch up with me <a href=https://telegram.me/mitjafelicijan target=_blank>on Telegram</a>.<hr><p>This website does not track you. Content is made available under the <a href=https://creativecommons.org/licenses/by/4.0/ target=_blank rel=noreferrer>CC BY 4.0 license</a> unless
specified otherwise. Blog is also available as <a href=/index.xml target=_blank>RSS feed</a>.</footer><script>
	    window.va = window.va || function () { (window.vaq = window.vaq || []).push(arguments); };
	  </script><script defer src=/_vercel/insights/script.js></script>