<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>~kls0e</title><link>https://tilde.town/~kls0e/</link><language>en-us</language><author>Matthias Klose</author><rights>(C) 2026</rights><updated>2026-04-20 23:06:04 +0200 CEST</updated><item><title>My Modern Personal Webpage Anthology - The Addendum</title><link>https://tilde.town/~kls0e/my-modern-personal-webpage-anthology-the-addendum/</link><pubDate>Mon, 20 Apr 2026 23:06:04 CEST</pubDate><author>Matthias Klose</author><guid>https://tilde.town/~kls0e/my-modern-personal-webpage-anthology-the-addendum/</guid><description>&lt;p&gt;I &lt;em&gt;knew&lt;/em&gt; I had it. This is a dump of &lt;strong&gt;matthias&amp;rsquo; webl&lt;/strong&gt; back from August 2010. It is the most recent data set available:&lt;/p&gt;
&lt;p&gt;&lt;img src="https://tilde.town/~kls0e/my-modern-personal-webpage-anthology-the-addendum/webl-2010-08.png" alt="screenshot of mdcp.de/blog as of August 2010" loading="lazy" decoding="async"&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;live snapshot:&lt;/strong&gt; &lt;a href="https://tilde.town/~kls0e/archaeology/2010-08/" target="_blank"&gt;mdcp.de/blog (2010)&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;This took all day to reenact. 16 years old undocumented WordPress 3.0 hacks and unorthodoxed CSS,
held together with lots of luck. It is part of the series
&lt;a href=https://tilde.town/~kls0e/my-modern-personal-webpage-anthology/
&gt;My Modern Personal Webpage Anthology&lt;/a&gt;. Can you find the easter egg? :-)&lt;/p&gt;</description></item><item><title>Printing Analog Photos on a Brother MFC-7360N</title><link>https://tilde.town/~kls0e/printing-analog-photos-on-a-brother-mfc-7360n/</link><pubDate>Sat, 18 Apr 2026 00:00:00 UTC</pubDate><author>Matthias Klose</author><guid>https://tilde.town/~kls0e/printing-analog-photos-on-a-brother-mfc-7360n/</guid><description>&lt;p&gt;I trained as a digital printer in Berlin back in 2005. Prepress, color management, the whole thing. Good prints, good vibes.&lt;/p&gt;
&lt;h2 id="the-goal"&gt;The Goal&lt;/h2&gt;
&lt;p&gt;Get the best possible black-and-white photo quality out of a monochrome laser printer using standard office paper and generic toner.&lt;/p&gt;
&lt;h2 id="the-source"&gt;The Source&lt;/h2&gt;
&lt;p&gt;The photographs come from
&lt;a href=https://jasmund.org
target=_blank rel="noopener noreferrer"
&gt;jasmund.org&lt;/a&gt;, my ongoing project analog photography project. Everything is
&lt;a href=https://tilde.town/~kls0e/jasmund.org/imprint#cameras--film
target=_blank rel="noopener noreferrer"
&gt;shot on 35mm film and scanned&lt;/a&gt;. With 16-bit grayscale TIFFs, depth carries a lot of tonal information, which makes it both a great starting point and a bit of a challenge when the output device is binary, just black dots on white paper.&lt;/p&gt;
&lt;h2 id="the-printer"&gt;The Printer&lt;/h2&gt;
&lt;p&gt;The
&lt;a href=https://www.brother.eu/-/media/product-downloads/devices/printers/mfc/mfc7360n/ie/mfc7360n.pdf
target=_blank rel="noopener noreferrer"
&gt;Brother MFC-7360N&lt;/a&gt; is a mono laser MFP released in 2010. It prints, scans, copies and faxes and is 16 years old as the time of writing. I picked mine up barely used on Kleinanzeigen in 2020 for € 20.– and I&amp;rsquo;ve been running it on generic toner (around € 12.– a cartridge, lasts for thousands of pages) since day one. It just keeps going, this is why Brother Lasers are my favourite.&lt;/p&gt;
&lt;p&gt;The whole CUPS AirPrint setup is covered in this post from 2020:
&lt;a href=https://tilde.town/~kls0e/add-airprint-to-a-brother-mfc-7360n/
&gt;AirPrint setup for a Brother MFC-7360N&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id="the-hardware-resolution"&gt;The Hardware Resolution&lt;/h2&gt;
&lt;p&gt;The MFC-7360N prints at up to 2400x600 dpi, that is asymmetric by design: 2400 dots in the horizontal direction from the laser, 600 dots per inch (2.54 cm) vertically from the stepper motor. The native dot shape is roughly four times wider than it is tall. Tiny straight hairlines ☰≡三 parallel to the long paper edge. A full A4 page at this resolution is 19840×7016 pixels.&lt;/p&gt;
&lt;p&gt;Brother&amp;rsquo;s own halftoning engine (&lt;code&gt;BRHalfTonePattern=Brother4&lt;/code&gt;) is tuned for this geometry. It handles midtones well and is a reasonable standard setting. But there is so much more you can get out of the hardware with a bit of pristine prepress preprocessing :-) what.&lt;/p&gt;
&lt;h2 id="dot-gain"&gt;Dot Gain&lt;/h2&gt;
&lt;p&gt;Laser toner spreads slightly further than desired during the fusing process. On regular office paper this is particularly noticeable: midtones shift towards shadows, prints come out too dark and lose shadow detail. This dot gain effect increases the smaller the print dots are.&lt;/p&gt;
&lt;p&gt;Dealing with dot gain in the image preprocessing — before the file ever reaches the printer driver — is most of what the following script is doing.&lt;/p&gt;
&lt;h2 id="the-imagemagick-pipeline"&gt;The ImageMagick Pipeline&lt;/h2&gt;
&lt;p&gt;After a plethora of test prints across different approaches, this is what I like best:&lt;/p&gt;
&lt;details class="code-collapse"&gt;
&lt;summary class="code-collapse-summary"&gt;
&lt;span class="code-collapse-label"&gt;show script&lt;/span&gt;
&lt;span class="code-collapse-meta"&gt;27 lines&lt;/span&gt;
&lt;/summary&gt;
&lt;div class="code-collapse-preview-wrap"&gt;
&lt;div class="code-collapse-preview"&gt;
&lt;pre&gt;&lt;code class="language-bash"&gt;magick &amp;amp;#34;$file&amp;amp;#34; \
-colorspace Gray \
-depth 16 \
-auto-orient \
-compress None \
-rotate &amp;amp;#34;%[fx:w&amp;amp;gt;h?90:0]&amp;amp;#34; \
-resize 4961x7016^ \
-gravity Center \
-extent 4961x7016 \
-gamma 1.65 \
-level 10%,95% \
-sigmoidal-contrast 3x55% \
-resize 19840x7016! \
-unsharp 0x1.0&amp;#43;0.45&amp;#43;0.02 \
-density 2400x600 -units PixelsPerInch \
pdf:- | \
lp -d Brother_MFC_7360N \
-o media=A4 \
-o BRResolution=2400x600dpi \
-o BRMediaType=Thick \
-o BRInputSlot=Tray1 \
-o print-quality=5 \
-o BRHalfTonePattern=Brother4 \
-o BRImproveOutput=BRFixIntensity \
-o BRReduceGhost=ON \
-o TonerSaveMode=OFF \
-o fit-to-page=false&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="code-collapse-full"&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;magick &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$file&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; -colorspace Gray &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; -depth &lt;span class="m"&gt;16&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; -auto-orient &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; -compress None &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; -rotate &lt;span class="s2"&gt;&amp;#34;%[fx:w&amp;gt;h?90:0]&amp;#34;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; -resize 4961x7016^ &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; -gravity Center &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; -extent 4961x7016 &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; -gamma 1.65 &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; -level 10%,95% &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; -sigmoidal-contrast 3x55% &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; -resize 19840x7016! &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; -unsharp 0x1.0+0.45+0.02 &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; -density 2400x600 -units PixelsPerInch &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; pdf:- &lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;lp -d Brother_MFC_7360N &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; -o &lt;span class="nv"&gt;media&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;A4 &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; -o &lt;span class="nv"&gt;BRResolution&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;2400x600dpi &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; -o &lt;span class="nv"&gt;BRMediaType&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;Thick &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; -o &lt;span class="nv"&gt;BRInputSlot&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;Tray1 &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; -o print-quality&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;5&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; -o &lt;span class="nv"&gt;BRHalfTonePattern&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;Brother4 &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; -o &lt;span class="nv"&gt;BRImproveOutput&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;BRFixIntensity &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; -o &lt;span class="nv"&gt;BRReduceGhost&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;ON &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; -o &lt;span class="nv"&gt;TonerSaveMode&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;OFF &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; -o fit-to-page&lt;span class="o"&gt;=&lt;/span&gt;false&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/details&gt;&lt;p&gt;Here is what each step does:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;-colorspace Gray -depth 16&lt;/code&gt;&lt;/strong&gt; — convert to grayscale immediately and stay in 16-bit. Rounding errors in 8-bit would be visible in smooth gradients.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;-auto-orient&lt;/code&gt;&lt;/strong&gt; — respects the EXIF rotation tag.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;-rotate &amp;quot;%[fx:w&amp;gt;h?90:0]&amp;quot;&lt;/code&gt;&lt;/strong&gt; — if the image is landscape, rotate it 90° so it fills an A4 sheet in portrait orientation. My goal is always maximum paper coverage, filling from the centre of the image.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;-resize 4961x7016^ -gravity Center -extent 4961x7016&lt;/code&gt;&lt;/strong&gt; — scale to fill A4 at 600 dpi (the vertical resolution) without distortion, then crop to exact page dimensions from the centre. The &lt;code&gt;^&lt;/code&gt; flag means fill, not fit so there&amp;rsquo;s no empty space beside the print borders.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;-gamma 1.65&lt;/code&gt;&lt;/strong&gt; — raises midtones to compensate for dot gain. Without this, toner spread pulls printed midtones noticeably into shadows. 1.65 is where it landed after a lot of comparison prints on 80g office paper.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;-level 10%,95%&lt;/code&gt;&lt;/strong&gt; — this clips the deepest shadows and the brightest highlights because the darkest tones will block up on this printer. Remapping them to a slightly lighter input value recovers some separation. The highlight clip keeps areas that should be white for subjective presence of the printed photo.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;-sigmoidal-contrast 3x55%&lt;/code&gt;&lt;/strong&gt; — this is advanced, an S-curve centred at 55% brightness with a strength of 3. This adds presence and micro-contrast to midtones (without clipping). It makes a real difference to how photographs feel on paper and makes the difference between flat and present.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;-resize 19840x7016!&lt;/code&gt;&lt;/strong&gt; — stretches the image 4x horizontally to match the native 2400×600 pixel buffer. The &lt;code&gt;!&lt;/code&gt; forces the exact target dimensions without preserving aspect ratio, as the image is already sized for 600 dpi vertically and it now needs to reach 2400 dpi horizontally. The printer receives a square-pixel image at the right density for the physical disproportional output.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;-unsharp 0x1.0+0.45+0.02&lt;/code&gt;&lt;/strong&gt; — a gentle unsharp mask applied after the 4x horizontal stretch. The stretch introduces slight softness. This brings back edge definition.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;-density 2400x600 -units PixelsPerInch&lt;/code&gt;&lt;/strong&gt; — embeds the resolution metadata in the PDF stream so the driver knows not to resample.&lt;/p&gt;
&lt;p&gt;On the &lt;code&gt;lp&lt;/code&gt; side: &lt;code&gt;BRHalfTonePattern=Brother4&lt;/code&gt; is Brother&amp;rsquo;s own clustered-dot screen, which at this resolution produces stable halftone cells with good tonal coverage. &lt;code&gt;BRReduceGhost=ON&lt;/code&gt; runs a drum cleaning sequence. &lt;code&gt;TonerSaveMode=OFF&lt;/code&gt; is for full toner density.&lt;/p&gt;
&lt;h2 id="the-script"&gt;The Script&lt;/h2&gt;
&lt;p&gt;The full script processes a directory of TIFFs in a loop. It rotates and fills the page for each image, and queues them to the printer with a two-second pause between jobs so the printer&amp;rsquo;s 32MB memory buffer does not conk out. mad maxamom. save as &lt;code&gt;mfprince-amen.sh&lt;/code&gt;.&lt;/p&gt;
&lt;details class="code-collapse"&gt;
&lt;summary class="code-collapse-summary"&gt;
&lt;span class="code-collapse-label"&gt;show script&lt;/span&gt;
&lt;span class="code-collapse-meta"&gt;50 lines&lt;/span&gt;
&lt;/summary&gt;
&lt;div class="code-collapse-preview-wrap"&gt;
&lt;div class="code-collapse-preview"&gt;
&lt;pre&gt;&lt;code class="language-bash"&gt;#!/bin/zsh
DIR=&amp;amp;#34;${1:-.}&amp;amp;#34;
if [ ! -d &amp;amp;#34;$DIR&amp;amp;#34; ]; then
echo &amp;amp;#34;Error: directory &amp;amp;#39;$DIR&amp;amp;#39; does not exist.&amp;amp;#34;
exit 1
fi
echo &amp;amp;#34;Searching for TIFF files in &amp;amp;#39;$DIR&amp;amp;#39;...&amp;amp;#34;
count=0
for file in &amp;amp;#34;$DIR&amp;amp;#34;/*.[tT][iI][fF]*; do
[ -e &amp;amp;#34;$file&amp;amp;#34; ] || continue
filename=$(basename &amp;amp;#34;$file&amp;amp;#34;)
echo &amp;amp;#34;Processing and printing: $filename&amp;amp;#34;
magick &amp;amp;#34;$file&amp;amp;#34; \
-colorspace Gray \
-depth 16 \
-auto-orient \
-compress None \
-rotate &amp;amp;#34;%[fx:w&amp;amp;gt;h?90:0]&amp;amp;#34; \
-resize 4961x7016^ \
-gravity Center \
-extent 4961x7016 \
-gamma 1.65 \
-level 10%,95% \
-sigmoidal-contrast 3x55% \
-resize 19840x7016! \
-unsharp 0x1.0&amp;#43;0.45&amp;#43;0.02 \
-density 2400x600 -units PixelsPerInch \
pdf:- | \
lp -d Brother_MFC_7360N \
-o media=A4 \
-o BRResolution=2400x600dpi \
-o BRMediaType=Thick \
-o BRInputSlot=Tray1 \
-o print-quality=5 \
-o BRHalfTonePattern=Brother4 \
-o BRImproveOutput=BRFixIntensity \
-o BRReduceGhost=ON \
-o TonerSaveMode=OFF \
-o fit-to-page=false
sleep 2
((count&amp;#43;&amp;#43;))
done
echo &amp;amp;#34;Done. $count print jobs queued.&amp;amp;#34;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="code-collapse-full"&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="cp"&gt;#!/bin/zsh
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nv"&gt;DIR&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;1&lt;/span&gt;&lt;span class="k"&gt;:-&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; ! -d &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$DIR&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;Error: directory &amp;#39;&lt;/span&gt;&lt;span class="nv"&gt;$DIR&lt;/span&gt;&lt;span class="s2"&gt;&amp;#39; does not exist.&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nb"&gt;exit&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;fi&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;Searching for TIFF files in &amp;#39;&lt;/span&gt;&lt;span class="nv"&gt;$DIR&lt;/span&gt;&lt;span class="s2"&gt;&amp;#39;...&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nv"&gt;count&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;for&lt;/span&gt; file in &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$DIR&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;/*.&lt;span class="o"&gt;[&lt;/span&gt;tT&lt;span class="o"&gt;][&lt;/span&gt;iI&lt;span class="o"&gt;][&lt;/span&gt;fF&lt;span class="o"&gt;]&lt;/span&gt;*&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="o"&gt;[&lt;/span&gt; -e &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$file&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="k"&gt;continue&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nv"&gt;filename&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;$(&lt;/span&gt;basename &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$file&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="k"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;Processing and printing: &lt;/span&gt;&lt;span class="nv"&gt;$filename&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; magick &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$file&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; -colorspace Gray &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; -depth &lt;span class="m"&gt;16&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; -auto-orient &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; -compress None &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; -rotate &lt;span class="s2"&gt;&amp;#34;%[fx:w&amp;gt;h?90:0]&amp;#34;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; -resize 4961x7016^ &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; -gravity Center &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; -extent 4961x7016 &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; -gamma 1.65 &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; -level 10%,95% &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; -sigmoidal-contrast 3x55% &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; -resize 19840x7016! &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; -unsharp 0x1.0+0.45+0.02 &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; -density 2400x600 -units PixelsPerInch &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; pdf:- &lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; lp -d Brother_MFC_7360N &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; -o &lt;span class="nv"&gt;media&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;A4 &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; -o &lt;span class="nv"&gt;BRResolution&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;2400x600dpi &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; -o &lt;span class="nv"&gt;BRMediaType&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;Thick &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; -o &lt;span class="nv"&gt;BRInputSlot&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;Tray1 &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; -o print-quality&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;5&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; -o &lt;span class="nv"&gt;BRHalfTonePattern&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;Brother4 &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; -o &lt;span class="nv"&gt;BRImproveOutput&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;BRFixIntensity &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; -o &lt;span class="nv"&gt;BRReduceGhost&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;ON &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; -o &lt;span class="nv"&gt;TonerSaveMode&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;OFF &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; -o fit-to-page&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;false&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; sleep &lt;span class="m"&gt;2&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="o"&gt;((&lt;/span&gt;count++&lt;span class="o"&gt;))&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;done&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;Done. &lt;/span&gt;&lt;span class="nv"&gt;$count&lt;/span&gt;&lt;span class="s2"&gt; print jobs queued.&amp;#34;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/details&gt;&lt;p&gt;Drop a folder of TIFFs in, give it time, watch the stack of prints materialise. The results hold shadow detail, render smooth gradients across the full tonal range, and reproduce fine texture — hairlines, clothing fabric, tree bark — well enough that I am genuinely happy with them.&lt;/p&gt;
&lt;p&gt;Every print is unique, thanks to its individual imperfections. The worn drum unit causes slight ghosting, and I am happy to embrace that. The used corona wire and the printer fuser unit cause some nuances in grayscale tones and slight variations in brightness in parallel to the short edge and such, but their aesthetic appeal is something I welcome. It is so far from perfect yet so full of joy.&lt;/p&gt;
&lt;p&gt;This machine is such a gift that keeps on givin'.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://tilde.town/~kls0e/printing-analog-photos-on-a-brother-mfc-7360n/analog-sw-photo-on-mono-laser-brother-7360n.jpg" alt="result" loading="lazy" decoding="async"&gt;
source:
&lt;a href=https://tilde.town/~kls0e/jasmund.org/westlich-von-altglobsow/#kls0e-wie-f%c3%bcr-uns-gemacht.png
target=_blank rel="noopener noreferrer"
&gt;westlich von Altglobsow on jasmund.org&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Moving on, let me now print all of my photos. Just to see how they come out.&lt;/p&gt;</description></item><item><title>My Modern Personal Webpage Anthology</title><link>https://tilde.town/~kls0e/my-modern-personal-webpage-anthology/</link><pubDate>Tue, 14 Apr 2026 16:51:10 CEST</pubDate><author>Matthias Klose</author><guid>https://tilde.town/~kls0e/my-modern-personal-webpage-anthology/</guid><description>&lt;p&gt;&lt;img src="https://tilde.town/~kls0e/my-modern-personal-webpage-anthology/y0s3n.gif" alt="y0s3n GIF from 2001" loading="lazy" decoding="async"&gt;&lt;/p&gt;
&lt;p&gt;Let me interject for a second — and turn back the hands of time: I am a millennial born
in 1985. &lt;em&gt;No go dey call me Gen Z.&lt;/em&gt; I got my first 286 PC in 1996, at age eleven. My
internet started with a 56k modem over analog landline — back when bandwidth was
precious and dial-up minutes cost real money. This shaped my data hoarding habits:
retaining backups of personal archives over decades and three system architectures,
including old websites. Under the handle &lt;strong&gt;madcap&lt;/strong&gt;, I built early pages from scratch,
later on WordPress and MySpace. How glad I am with
&lt;a href=https://gohugo.io/
target=_blank rel="noopener noreferrer"
&gt;Hugo&lt;/a&gt; these days.&lt;/p&gt;
&lt;p&gt;Here are some authentic sparkling
&lt;a href=https://tilde.town/~westwind/
target=_blank rel="noopener noreferrer"
&gt;emeralds&lt;/a&gt; in my mother tongue German. I thank myself for bearing with me.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id="2005-the-htmlcss-era"&gt;2005: the HTML/CSS era&lt;/h3&gt;
&lt;p&gt;&lt;img src="https://tilde.town/~kls0e/my-modern-personal-webpage-anthology/2005-08.png" alt="madcaps-page.de (2005)" loading="lazy" decoding="async"&gt;&lt;/p&gt;
&lt;p&gt;The oldest dataset is 21 years old at the time of writing. It originally lived under
&lt;code&gt;madcaps-page.de&lt;/code&gt; and consists purely of HTML and CSS. Because of its simplicity, this
one was easy: I let
&lt;a href=https://www.httrack.com
target=_blank rel="noopener noreferrer"
&gt;HTTrack&lt;/a&gt; walk over it to consolidate the
site structure. Afterwards I manually fixed 404&amp;rsquo;ing links and removed external content such as the obligatory externally hosted guestbook (and also a t-shirt shop ???) - ready to reinstantiate.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;live snapshot:&lt;/strong&gt; &lt;a href="https://tilde.town/~kls0e/archaeology/2005-08/" target="_blank"&gt;madcaps-page.de (2005)&lt;/a&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id="2008--2010-the-wordpress-era"&gt;2008 – 2010: the WordPress era&lt;/h3&gt;
&lt;p&gt;&lt;img src="https://tilde.town/~kls0e/my-modern-personal-webpage-anthology/2009-09.png" alt="mdcp.de/blog (2009)" loading="lazy" decoding="async"&gt;&lt;/p&gt;
&lt;p&gt;The second dataset is a 17-year-old &lt;code&gt;wwwroot&lt;/code&gt; and MySQL dump of a WordPress blog that
resided under &lt;code&gt;mdcp.de/blog&lt;/code&gt;. I spun up two local Docker instances providing Apache with
PHP 7.4 and MariaDB. The database import worked flawlessly.&lt;/p&gt;
&lt;p&gt;I remember having a couple of WordPress security plugins installed back in the day, which
initially interfered with getting this ancient dump to render in the browser. After a
couple of hours of debugging, I simply disabled all the legacy plugins and replaced the
core files with their current successors — that is four major releases from 2.8.4 to
6.9.1. Impressively enough, it just worked.&lt;/p&gt;
&lt;p&gt;Of course I did not plan to maintain this WordPress instance any further. To preserve a
static impression of the blog, I came across
&lt;a href=https://crates.io/crates/monolith
target=_blank rel="noopener noreferrer"
&gt;monolith&lt;/a&gt;. Written in Rust, it creates non-recursive webpage snapshots by bundling all assets into a single &lt;code&gt;.html&lt;/code&gt; file using base64 inline encoding. It&amp;rsquo;s wonderful. I just had to fix countless internal links by replacing most of the &lt;code&gt;href&lt;/code&gt; attributes to point to &lt;code&gt;#&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;live snapshot:&lt;/strong&gt; &lt;a href="https://tilde.town/~kls0e/archaeology/2009-09/" target="_blank"&gt;mdcp.de/blog (2009)&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src="https://tilde.town/~kls0e/my-modern-personal-webpage-anthology/2010-08.png" alt="mdcp.de/blog (2010)" loading="lazy" decoding="async"&gt;&lt;/p&gt;
&lt;p&gt;A couple of days in, I was able to restore a dump of &lt;strong&gt;matthias&amp;rsquo; webl&lt;/strong&gt; as of August 2010. That is the most current iteration. See the
&lt;a href=https://tilde.town/~kls0e/my-modern-personal-webpage-anthology-the-addendum/
&gt;addendum post&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;live snapshot:&lt;/strong&gt; &lt;a href="https://tilde.town/~kls0e/archaeology/2010-08/" target="_blank"&gt;mdcp.de/blog (2010)&lt;/a&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id="2009-the-myspace-era"&gt;2009: the MySpace era&lt;/h3&gt;
&lt;p&gt;&lt;img src="https://tilde.town/~kls0e/my-modern-personal-webpage-anthology/2009-11-myspace.png" alt="myspace.com/matthi4s (2009)" loading="lazy" decoding="async"&gt;&lt;/p&gt;
&lt;p&gt;A bonus dataset is a 17-year-old &amp;ldquo;File &amp;gt; Save as&amp;hellip;&amp;rdquo; dump from Internet Explorer 7,
containing my former MySpace profile &lt;code&gt;myspace.com/matthi4s&lt;/code&gt;. MySpace was the pioneering
browser social network with wild CSS hacks. I archived this one using &lt;code&gt;monolith --isolate&lt;/code&gt; to prevent the archived version from calling any external JavaScript resources
or tracking pixels. Some manual HTML sanitation took place as well, and there it is.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;live snapshot:&lt;/strong&gt; &lt;a href="https://tilde.town/~kls0e/archaeology/2009-11/" target="_blank"&gt;myspace.com/matthi4s (2009)&lt;/a&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id="2012-tumblr-the-one-or-two-that-got-away"&gt;2012: Tumblr, the One or Two that got Away&lt;/h3&gt;
&lt;p&gt;&lt;img src="https://tilde.town/~kls0e/my-modern-personal-webpage-anthology/matthia.sklo.se.png" alt="matthia.sklo.se on the WayBack Machine" loading="lazy" decoding="async"&gt;&lt;/p&gt;
&lt;p&gt;There also were &lt;code&gt;matthia.sklo.se&lt;/code&gt; (photos) and &lt;code&gt;geschwollen.org&lt;/code&gt; (poems). These were tumblr-based. I do not have backups. But at least I have reclaimed the sklo.se domain. The invaluable WayBack Machine passed
by a couple of times:
&lt;a href=https://web.archive.org/web/20121029113826/http://matthia.sklo.se/
target=_blank rel="noopener noreferrer"
&gt;https://matthia.sklo.se on web.archive.org&lt;/a&gt; – What is your personal Voyager golden record? :-)&lt;/p&gt;</description></item><item><title>Tildeverse, one Box at a Time</title><link>https://tilde.town/~kls0e/tildeverse-one-box-at-a-time/</link><pubDate>Tue, 14 Apr 2026 02:30:15 CEST</pubDate><author>Matthias Klose</author><guid>https://tilde.town/~kls0e/tildeverse-one-box-at-a-time/</guid><description>&lt;p&gt;The tildeverse is a loosely associated network of free pubnix systems, text based servers to be creative on and do fun stuff with.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://tilde.town/~kls0e/tildeverse-one-box-at-a-time/tilde.town.png" alt="tilde.town screenshot" loading="lazy" decoding="async"&gt;&lt;/p&gt;
&lt;p&gt;This page is hosted at
&lt;a href=https://tilde.town
target=_blank rel="noopener noreferrer"
&gt;tilde.town&lt;/a&gt;, a pubnix by
&lt;a href=https://tilde.town/~vilmibm/
target=_blank rel="noopener noreferrer"
&gt;~vilmibm&lt;/a&gt; and I just came across this opportunity to randomly explore it: &lt;div id="tilde_ring" style="background-color:; text-align:; border-radius:px;"&gt;
&lt;p&gt;&lt;a id="tilde_town_ring" class="tilde_ring_link" href="" target="_blank"&gt;random ~user&lt;/a&gt; / &lt;a id="random_tildebox" class="tilde_ring_link" href="" target="_blank"&gt;random ~box&lt;/a&gt; / &lt;a id="tilde_town_ring_next" class="tilde_ring_link" href="" target="_blank"&gt;next ~user&lt;/a&gt; / &lt;a href="https://tilde.town/~eeeeeta/ring/join.html" target="_blank"&gt;join&lt;/a&gt;&lt;/p&gt;
&lt;script type="text/javascript"&gt;
var ringjs = document.createElement('script');
ringjs.src = document.location.protocol + '//tilde.town/~login/tilde_ring.js';
document.getElementById('tilde_ring').appendChild(ringjs);
&lt;/script&gt;
&lt;/div&gt;
&lt;/p&gt;
&lt;p&gt;Explorational links are also always available from the imprint from now on.&lt;/p&gt;
&lt;div class="tilde-updates"&gt;
latest tilde.town updates (thanks &lt;a href="https://tilde.town/~ags/updated.html" target="_blank"&gt;~ags&lt;/a&gt;): &lt;div id="updates-content"&gt;Loading...&lt;/div&gt;
&lt;/div&gt;
&lt;script&gt;
async function loadUpdates() {
try {
const response = await fetch('https://tilde.town/~ags/updated.json');
const json = await response.json();
let items = (json.pagelist || []).slice(5, 15);
items.sort(() =&gt; 0.5 - Math.random());
const content = document.getElementById('updates-content');
if (!items.length) {
content.textContent = 'No updates.';
return;
}
content.innerHTML = items.map((item, i) =&gt; {
const user = item.username || 'unknown';
const href = item.homepage ? item.homepage.replace(/^\/\//, 'https://') : `https://tilde.town/~${user}/`;
const time = new Date(item.modtime).toLocaleString();
const slash = i &lt; items.length - 1 ? ' / ' : '';
return `&lt;a href="${href}" target="_blank" rel="noopener noreferrer" title="${time}"&gt;~${user}&lt;/a&gt;${slash}`;
}).join('');
} catch (error) {
console.error('Fetch failed:', error);
document.getElementById('updates-content').textContent = 'Load failed.';
}
}
loadUpdates();
setInterval(loadUpdates, 1800000);
&lt;/script&gt;</description></item><item><title>TIME.TAXI – an Ode to Escapism</title><link>https://tilde.town/~kls0e/time.taxi-an-ode-to-escapism/</link><pubDate>Mon, 13 Apr 2026 19:12:44 CEST</pubDate><author>Matthias Klose</author><guid>https://tilde.town/~kls0e/time.taxi-an-ode-to-escapism/</guid><description>&lt;p&gt;With some time on my hands, I decided to reissue a piece of internet art called &lt;strong&gt;TIME.TAXI&lt;/strong&gt;. It has been sitting on my ssd for the longest time, and its URL is squatted and stuff, but from now on, its sanctuary shall be tilde.town. &lt;em&gt;&lt;strong&gt;sanctuary, god, I love this word.&lt;/strong&gt;&lt;/em&gt; but please see for yourself:&lt;/p&gt;
&lt;p&gt;&lt;img src="https://tilde.town/~kls0e/time.taxi-an-ode-to-escapism/time-taxi.png" alt="screenshot of TIME.TAXI" loading="lazy" decoding="async"&gt;&lt;/p&gt;
&lt;h3 id="httpstildetown"&gt;
&lt;a href=https://tilde.town/~kls0e/time.taxi
target=_blank rel="noopener noreferrer"
&gt;https://tilde.town/~kls0e/time.taxi&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;by the way, while running the terminal-based email client &amp;lsquo;mutt&amp;rsquo; for the first time yesterday while ssh&amp;rsquo;d into tilde.town, I&amp;rsquo;ve learned a couple of interesting things about this tildeverse:&lt;/p&gt;
&lt;h3 id="there-is-a-tilde-town-zine"&gt;there is a tilde town zine:
&lt;a href=https://tilde.town/~zine
target=_blank rel="noopener noreferrer"
&gt;https://tilde.town/~zine&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;fwiw, it&amp;rsquo;s super liberal, friendly and interesting to read. I&amp;rsquo;m looking to release one of my photos from
&lt;a href=https://jasmund.org
target=_blank rel="noopener noreferrer"
&gt;jasmund.org&lt;/a&gt; in the coming issue.&lt;/p&gt;
&lt;h3 id="there-is-a-tilderadio"&gt;there is a tilderadio:
&lt;a href=https://tilderadio.org
target=_blank rel="noopener noreferrer"
&gt;https://tilderadio.org&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;I essentially listen to online radio every day, my favourite stations are mitunter
&lt;a href=https://www.radioswissjazz.ch
target=_blank rel="noopener noreferrer"
&gt;Radio Swiss Jazz&lt;/a&gt;,
&lt;a href=https://www.nts.live
target=_blank rel="noopener noreferrer"
&gt;NTS&lt;/a&gt;,
&lt;a href=https://www.perfectmoods.com
target=_blank rel="noopener noreferrer"
&gt;PerfectMoods&lt;/a&gt;,
&lt;a href=https://somafm.com/groovesalad/
target=_blank rel="noopener noreferrer"
&gt;Groove Salad&lt;/a&gt; and today I&amp;rsquo;m actively listening to tilderadio. So far, it&amp;rsquo;s a refreshing blend of vintage radio commercials, oldschool hip hop and wild punk. excellent. My macOS Receiver Radio app does have some buffering issues with it, so VLC is back at it.&lt;/p&gt;</description></item><item><title>Mapping a Beauty R1 Bluetooth Remote to Kodi on Batocera</title><link>https://tilde.town/~kls0e/mapping-a-beauty-r1-bluetooth-remote-to-kodi-on-batocera/</link><pubDate>Sat, 14 Mar 2026 00:00:00 UTC</pubDate><author>Matthias Klose</author><guid>https://tilde.town/~kls0e/mapping-a-beauty-r1-bluetooth-remote-to-kodi-on-batocera/</guid><description>&lt;div class="post-intro"&gt;
&lt;div class="post-intro-image"&gt;
&lt;img src="https://tilde.town/~kls0e/mapping-a-beauty-r1-bluetooth-remote-to-kodi-on-batocera/beauty-r1-bluetooth-remote.jpg" alt="Beauty R1 remote control"&gt;
&lt;/div&gt;
&lt;div class="post-intro-text"&gt;
&lt;p&gt;
A friend is running &lt;a href="https://batocera.org/"&gt;Batocera&lt;/a&gt; as a media/emulation box and wanted to use a &lt;strong&gt;Beauty R1 Bluetooth remote&lt;/strong&gt; with Kodi. The R1 is a hyper-affordable bluetooth media remote — and it emits mouse movement events instead of regular key presses, and Batocera's underlying buildroot Linux has no idea what to do with it out of the box. It pairs just fine using Bluetooth 4.2 tho. &lt;code&gt;evtest&lt;/code&gt; does show some output on each button press. But nothing seems to happen in the EmulationStation or Kodi GUI.&lt;br&gt;
The fix is a small Python daemon that grabs the R1's input device exclusively and translates its events into keyboard events Kodi understands.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;h2 id="why-its-tricky"&gt;Why it&amp;rsquo;s tricky&lt;/h2&gt;
&lt;p&gt;Running &lt;code&gt;evtest&lt;/code&gt; on the device reveals something challenging: every directional press
arrives as &lt;strong&gt;three separate event groups&lt;/strong&gt; before &lt;code&gt;BTN_LEFT&lt;/code&gt; is even set:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;REL_X=-2047, REL_Y=+2047 ← calibration spike (ignore!)
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;REL_X=+148, REL_Y=-354 ← return-to-center offset
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;BTN_LEFT=1 ← actual button down
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;REL_Y=+77 (×2) ← real directional data
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;BTN_LEFT=0 ← button up&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;all that REL data before the button press results in &lt;code&gt;RIGHT&lt;/code&gt; + &lt;code&gt;ENTER&lt;/code&gt; on every single keystroke. Took a while to figure out.&lt;/p&gt;
&lt;p&gt;The other fun one: the &lt;strong&gt;Camcorder button&lt;/strong&gt; sends the same BTN_LEFT sequence as the directional pad and center button — but its pre-BTN_LEFT offset is &lt;code&gt;REL_Y ≈ -30&lt;/code&gt; instead of &lt;code&gt;≈ -354&lt;/code&gt;. That tiny difference is the only way to tell them apart.&lt;/p&gt;
&lt;h2 id="the-solution"&gt;The solution&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Ignore all REL events with |value| ≥ 1000&lt;/strong&gt; — these
calibration spikes and should be discarded.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Track &lt;code&gt;pre_y&lt;/code&gt; before &lt;code&gt;BTN_LEFT=1&lt;/code&gt;&lt;/strong&gt; — if &lt;code&gt;abs(pre_y) &amp;lt; 100&lt;/code&gt;, it&amp;rsquo;s the
Camcorder button → send &lt;code&gt;KEY_ESC&lt;/code&gt;. Otherwise it&amp;rsquo;s probably a nav key or center.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;direction_sent&lt;/code&gt; flag&lt;/strong&gt; — once a direction has been emitted, suppress
the ENTER that would follow on &lt;code&gt;BTN_LEFT=0&lt;/code&gt;.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id="key-mapping"&gt;Key mapping&lt;/h2&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Beauty R1 button&lt;/th&gt;
&lt;th&gt;Sends&lt;/th&gt;
&lt;th&gt;Kodi action&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;D-pad Up&lt;/td&gt;
&lt;td&gt;KEY_UP&lt;/td&gt;
&lt;td&gt;Navigate up&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;D-pad Down&lt;/td&gt;
&lt;td&gt;KEY_DOWN&lt;/td&gt;
&lt;td&gt;Navigate down&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;D-pad Left&lt;/td&gt;
&lt;td&gt;KEY_LEFT&lt;/td&gt;
&lt;td&gt;Navigate left / rewind&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;D-pad Right&lt;/td&gt;
&lt;td&gt;KEY_RIGHT&lt;/td&gt;
&lt;td&gt;Navigate right / forward&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Center (OK)&lt;/td&gt;
&lt;td&gt;KEY_ENTER&lt;/td&gt;
&lt;td&gt;Select / Play-Pause&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Home&lt;/td&gt;
&lt;td&gt;KEY_VOLUMEDOWN&lt;/td&gt;
&lt;td&gt;Volume down&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Camera&lt;/td&gt;
&lt;td&gt;KEY_VOLUMEUP&lt;/td&gt;
&lt;td&gt;Volume up&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Camcorder&lt;/td&gt;
&lt;td&gt;KEY_ESC&lt;/td&gt;
&lt;td&gt;Back / close menu&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Note: the Camera button sends &lt;code&gt;KEY_VOLUMEDOWN&lt;/code&gt; — the script remaps it to &lt;code&gt;KEY_VOLUMEUP&lt;/code&gt;. The Home button sends &lt;code&gt;KEY_HOMEPAGE&lt;/code&gt; — remapped to &lt;code&gt;KEY_VOLUMEDOWN&lt;/code&gt; because that&amp;rsquo;s the desired remote button layout of choice.&lt;/p&gt;
&lt;h2 id="the-script"&gt;The script&lt;/h2&gt;
&lt;p&gt;Save to &lt;code&gt;/userdata/system/beauty_r1_mapper.py&lt;/code&gt;:&lt;/p&gt;
&lt;details class="code-collapse"&gt;
&lt;summary class="code-collapse-summary"&gt;
&lt;span class="code-collapse-label"&gt;show script&lt;/span&gt;
&lt;span class="code-collapse-meta"&gt;136 lines&lt;/span&gt;
&lt;/summary&gt;
&lt;div class="code-collapse-preview-wrap"&gt;
&lt;div class="code-collapse-preview"&gt;
&lt;pre&gt;&lt;code class="language-python"&gt;#!/usr/bin/env python3
&amp;amp;#34;&amp;amp;#34;&amp;amp;#34;
Beauty R1 Bluetooth Remote → Kodi Keyboard Mapper
&amp;amp;#34;&amp;amp;#34;&amp;amp;#34;
import evdev
from evdev import InputDevice, UInput, ecodes
import sys
e = ecodes
DEVICE_NAMES = [&amp;amp;#34;Beauty&amp;amp;#34;, &amp;amp;#34;beauty&amp;amp;#34;, &amp;amp;#34;R1&amp;amp;#34;, &amp;amp;#34;r1&amp;amp;#34;]
THRESHOLD = 50
SPIKE = 1000
def find_beauty_r1():
devices = [InputDevice(path) for path in evdev.list_devices()]
for device in devices:
for name in DEVICE_NAMES:
if name in device.name:
print(f&amp;amp;#34;Found: {device.name} ({device.path})&amp;amp;#34;)
return device
print(&amp;amp;#34;Beauty R1 not found. Available devices:&amp;amp;#34;)
for device in devices:
print(f&amp;amp;#34; - {device.name} ({device.path})&amp;amp;#34;)
return None
def main():
r1_device = find_beauty_r1()
if not r1_device:
sys.exit(1)
try:
r1_device.grab()
except IOError as err:
print(f&amp;amp;#34;Failed to grab device: {err}&amp;amp;#34;)
sys.exit(1)
capabilities = {
e.EV_KEY: [
e.KEY_UP, e.KEY_DOWN, e.KEY_LEFT, e.KEY_RIGHT,
e.KEY_ENTER, e.KEY_ESC, e.KEY_VOLUMEUP, e.KEY_VOLUMEDOWN,
]
}
ui = UInput(capabilities, name=&amp;amp;#34;Beauty R1 Virtual Keyboard&amp;amp;#34;)
print(&amp;amp;#34;Mapper running. Ctrl&amp;#43;C to quit.&amp;amp;#34;)
button_pressed = False
direction_sent = False
is_camcorder = False
pre_y = 0
post_x = 0
post_y = 0
try:
for event in r1_device.read_loop():
if event.type == e.EV_REL:
if abs(event.value) &amp;amp;gt;= SPIKE:
continue
if not button_pressed:
if event.code == e.REL_Y:
pre_y &amp;#43;= event.value
else:
if not direction_sent:
if event.code == e.REL_X:
post_x &amp;#43;= event.value
elif event.code == e.REL_Y:
post_y &amp;#43;= event.value
elif event.type == e.EV_KEY:
if event.code == e.BTN_LEFT:
if event.value == 1:
button_pressed = True
direction_sent = False
post_x = 0
post_y = 0
is_camcorder = abs(pre_y) &amp;amp;lt; 100
pre_y = 0
elif event.value == 0:
if is_camcorder:
ui.write(e.EV_KEY, e.KEY_ESC, 1)
ui.write(e.EV_KEY, e.KEY_ESC, 0)
ui.syn()
print(&amp;amp;#34;→ ESC (Camcorder)&amp;amp;#34;)
elif not direction_sent:
ui.write(e.EV_KEY, e.KEY_ENTER, 1)
ui.write(e.EV_KEY, e.KEY_ENTER, 0)
ui.syn()
print(&amp;amp;#34;→ ENTER&amp;amp;#34;)
button_pressed = False
direction_sent = False
is_camcorder = False
post_x = 0
post_y = 0
elif event.code == e.KEY_HOMEPAGE:
if event.value == 1:
ui.write(e.EV_KEY, e.KEY_VOLUMEDOWN, 1)
ui.write(e.EV_KEY, e.KEY_VOLUMEDOWN, 0)
ui.syn()
print(&amp;amp;#34;→ VOLUME DOWN (Home)&amp;amp;#34;)
elif event.code == e.KEY_VOLUMEDOWN:
if event.value == 1:
ui.write(e.EV_KEY, e.KEY_VOLUMEUP, 1)
ui.write(e.EV_KEY, e.KEY_VOLUMEUP, 0)
ui.syn()
print(&amp;amp;#34;→ VOLUME UP (Camera)&amp;amp;#34;)
elif event.type == e.EV_SYN:
if button_pressed and not direction_sent and not is_camcorder:
if abs(post_x) &amp;amp;gt;= THRESHOLD or abs(post_y) &amp;amp;gt;= THRESHOLD:
if abs(post_y) &amp;amp;gt;= abs(post_x):
key, name = (e.KEY_UP, &amp;amp;#34;UP&amp;amp;#34;) if post_y &amp;amp;gt; 0 \
else (e.KEY_DOWN, &amp;amp;#34;DOWN&amp;amp;#34;)
else:
key, name = (e.KEY_LEFT, &amp;amp;#34;LEFT&amp;amp;#34;) if post_x &amp;amp;gt; 0 \
else (e.KEY_RIGHT, &amp;amp;#34;RIGHT&amp;amp;#34;)
ui.write(e.EV_KEY, key, 1)
ui.write(e.EV_KEY, key, 0)
ui.syn()
print(f&amp;amp;#34;→ {name}&amp;amp;#34;)
direction_sent = True
post_x = 0
post_y = 0
except KeyboardInterrupt:
print(&amp;amp;#34;\nStopped.&amp;amp;#34;)
finally:
r1_device.ungrab()
ui.close()
if __name__ == &amp;amp;#34;__main__&amp;amp;#34;:
main()&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="code-collapse-full"&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="ch"&gt;#!/usr/bin/env python3&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt;&amp;#34;&amp;#34;&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt;Beauty R1 Bluetooth Remote → Kodi Keyboard Mapper
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt;&amp;#34;&amp;#34;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;evdev&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;evdev&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;InputDevice&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;UInput&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ecodes&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;sys&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;e&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ecodes&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;DEVICE_NAMES&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;Beauty&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;beauty&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;R1&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;r1&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;THRESHOLD&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;50&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;SPIKE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;find_beauty_r1&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;devices&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;InputDevice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;path&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;evdev&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;list_devices&lt;/span&gt;&lt;span class="p"&gt;()]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;device&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;devices&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;DEVICE_NAMES&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;device&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;Found: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;device&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; (&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;device&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;)&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;device&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;Beauty R1 not found. Available devices:&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;device&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;devices&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34; - &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;device&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; (&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;device&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;)&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;None&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;r1_device&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;find_beauty_r1&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;r1_device&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;exit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;r1_device&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;grab&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="ne"&gt;IOError&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;Failed to grab device: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;exit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;capabilities&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;EV_KEY&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;KEY_UP&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;KEY_DOWN&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;KEY_LEFT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;KEY_RIGHT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;KEY_ENTER&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;KEY_ESC&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;KEY_VOLUMEUP&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;KEY_VOLUMEDOWN&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;ui&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;UInput&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;capabilities&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;Beauty R1 Virtual Keyboard&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;Mapper running. Ctrl+C to quit.&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;button_pressed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;False&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;direction_sent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;False&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;is_camcorder&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;False&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;pre_y&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;post_x&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;post_y&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;event&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;r1_device&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;read_loop&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;type&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;EV_REL&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nb"&gt;abs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="n"&gt;SPIKE&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;continue&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;button_pressed&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;code&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;REL_Y&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;pre_y&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;direction_sent&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;code&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;REL_X&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;post_x&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;elif&lt;/span&gt; &lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;code&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;REL_Y&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;post_y&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;elif&lt;/span&gt; &lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;type&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;EV_KEY&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;code&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;BTN_LEFT&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;button_pressed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;True&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;direction_sent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;False&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;post_x&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;post_y&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;is_camcorder&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;abs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pre_y&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;pre_y&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;elif&lt;/span&gt; &lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;is_camcorder&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;ui&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;EV_KEY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;KEY_ESC&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;ui&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;EV_KEY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;KEY_ESC&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;ui&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;syn&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;→ ESC (Camcorder)&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;elif&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;direction_sent&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;ui&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;EV_KEY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;KEY_ENTER&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;ui&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;EV_KEY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;KEY_ENTER&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;ui&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;syn&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;→ ENTER&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;button_pressed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;False&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;direction_sent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;False&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;is_camcorder&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;False&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;post_x&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;post_y&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;elif&lt;/span&gt; &lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;code&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;KEY_HOMEPAGE&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;ui&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;EV_KEY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;KEY_VOLUMEDOWN&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;ui&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;EV_KEY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;KEY_VOLUMEDOWN&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;ui&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;syn&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;→ VOLUME DOWN (Home)&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;elif&lt;/span&gt; &lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;code&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;KEY_VOLUMEDOWN&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;ui&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;EV_KEY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;KEY_VOLUMEUP&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;ui&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;EV_KEY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;KEY_VOLUMEUP&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;ui&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;syn&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;→ VOLUME UP (Camera)&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;elif&lt;/span&gt; &lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;type&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;EV_SYN&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;button_pressed&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;direction_sent&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;is_camcorder&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nb"&gt;abs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;post_x&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="n"&gt;THRESHOLD&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="nb"&gt;abs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;post_y&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="n"&gt;THRESHOLD&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nb"&gt;abs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;post_y&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="nb"&gt;abs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;post_x&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;KEY_UP&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;UP&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;post_y&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; \
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;KEY_DOWN&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;DOWN&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;KEY_LEFT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;LEFT&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;post_x&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; \
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;KEY_RIGHT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;RIGHT&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;ui&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;EV_KEY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;ui&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;EV_KEY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;ui&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;syn&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;→ &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;direction_sent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;True&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;post_x&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;post_y&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="ne"&gt;KeyboardInterrupt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;Stopped.&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;finally&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;r1_device&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ungrab&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;ui&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;close&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="vm"&gt;__name__&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;__main__&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/details&gt;&lt;p&gt;make it executable: &lt;code&gt;chmod +x /userdata/system/beauty_r1_mapper.py&lt;/code&gt;&lt;br&gt;
test with &lt;code&gt;python3 /userdata/system/beauty_r1_mapper.py&lt;/code&gt;&lt;/p&gt;
&lt;h2 id="using-it-on-android"&gt;Using it on Android&lt;/h2&gt;
&lt;p&gt;If you aren&amp;rsquo;t running Linux/Batocera but rather an Android device and want to fix the Beauty R1 remote&amp;rsquo;s mapping, check out Luca&amp;rsquo;s work on GitHub:&lt;/p&gt;
&lt;p&gt;
&lt;a href=https://github.com/olivluca/beauty-r1-android-interceptor
target=_blank rel="noopener noreferrer"
&gt;beauty-r1-android-interceptor by olivluca&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Luca came up with logic to intercept and translate the remote&amp;rsquo;s events on Android and wrote a C program to do so.&lt;/p&gt;</description></item><item><title>beautiful colors on Thinkpad T430 using this ICC Profile 🏳️‍🌈⃤</title><link>https://tilde.town/~kls0e/beautiful-colors-on-thinkpad-t430-using-this-icc-profile-%EF%B8%8F%E2%83%A4/</link><pubDate>Sun, 02 Apr 2023 15:37:44 CEST</pubDate><author>Matthias Klose</author><guid>https://tilde.town/~kls0e/beautiful-colors-on-thinkpad-t430-using-this-icc-profile-%EF%B8%8F%E2%83%A4/</guid><description>&lt;p&gt;A friend I have met via Freifunk Berlin sold me his
&lt;a href=https://www.thinkwiki.org/wiki/Category:T430
target=_blank rel="noopener noreferrer"
&gt;Thinkpad T430&lt;/a&gt;. It is a great, robust and versatile machine and I use it almost daily with a posix-compatible OS. The colors of its TN panel are a bit bold on the blue side. There are devices to measure screen colors and there is software to correct and balance them. This process is called
&lt;a href=https://www.adobe.com/creativecloud/video/discover/how-to-calibrate-monitor.html
target=_blank rel="noopener noreferrer"
&gt;monitor calibration&lt;/a&gt; and usually results in an .icc file containing the custom color corrections. The GPU will essentially translate its color output according to the values read from the ICC profile, be it a standard or a custom output profile. A custom ICC profile can be applied to a graphical output device on every major (and not so major) OS. The GPU will be just as powerful as with a standard profile.&lt;br&gt;&lt;/p&gt;
&lt;p&gt;Back in 2012, notebookcheck.net has published a thorough review of the T430. They have also tested the machines panel and were kind enough to publish the ICC profile generated for it. Please look for the words &lt;b&gt;&amp;ldquo;Download ICC File (X-Rite i1Display 2)&amp;quot;&lt;/b&gt; on their
&lt;a href=https://www.notebookcheck.net/Review-Lenovo-ThinkPad-T430-Notebook.81802.0.html
target=_blank rel="noopener noreferrer"
&gt;article page&lt;/a&gt;. &lt;br&gt;&lt;/p&gt;
&lt;p&gt;Once downloaded, apply the ICC profile to your T430 display. The ICC profile selection menu of your OS will show a new entry that reads &lt;code&gt;Monitor_20.07.2012_1_01&lt;/code&gt; if you did move the .icc file to the right place.&lt;br&gt;&lt;/p&gt;
&lt;p&gt;Overall impression is much nicer and friendlier now. Natural &amp;amp; vivid colors. It is worth it. Watch a video and compare.&lt;/p&gt;</description></item><item><title>Aiyima T9 Pro with HifiberryOS 🎶</title><link>https://tilde.town/~kls0e/aiyima-t9-pro-with-hifiberryos/</link><pubDate>Sat, 01 Apr 2023 07:30:00 CEST</pubDate><author>Matthias Klose</author><guid>https://tilde.town/~kls0e/aiyima-t9-pro-with-hifiberryos/</guid><description>&lt;p&gt;It was in the mid 1990s when my godfather introduced me to his passion of music reproduction, praising the great speakers by the small label of
&lt;a href=https://www.orbid-sound.de
target=_blank rel="noopener noreferrer"
&gt;Orbid Sound&lt;/a&gt; from
&lt;a href=https://en.wikipedia.org/wiki/Odenwald
target=_blank rel="noopener noreferrer"
&gt;Odenwald&lt;/a&gt; area, Germany. Amazed at how good music can sound (even if compressed in
&lt;a href=https://en.wikipedia.org/wiki/ATRAC
target=_blank rel="noopener noreferrer"
&gt;ATRAC&lt;/a&gt; codec using Sony
&lt;a href=https://en.wikipedia.org/wiki/MiniDisc
target=_blank rel="noopener noreferrer"
&gt;Minidisc&lt;/a&gt; further on), I was now a Hifi enthusiast as well.&lt;br&gt;
When I was 14, I was lucky enough of being able to afford a small
&lt;a href=https://www.hifi-wiki.de/index.php/Technics_SC-HD310
target=_blank rel="noopener noreferrer"
&gt;technics SC HD-310 stereo&lt;/a&gt; investing all of my &lt;i&gt;confirmation money&lt;/i&gt;. I did use it as my main (and only :-)) system 10+ years from that.&lt;br&gt;
It must have been around the early 2010s when I first got to know about
&lt;a href=https://en.wikipedia.org/wiki/Class-D_amplifier
target=_blank rel="noopener noreferrer"
&gt;Class D Amplifiers&lt;/a&gt;. As of writing this, it is 2023 I am still into that.&lt;/p&gt;
&lt;h3 id="main-system-and-signal-path"&gt;Main System and Signal Path&lt;/h3&gt;
&lt;p&gt;
&lt;a href=https://www.aiyima.com/
target=_blank rel="noopener noreferrer"
&gt;Aiyima&lt;/a&gt; does a pretty good job at designing and manufacturing Class D Amps, I would say. For their
&lt;a href=https://www.audiosciencereview.com/forum/index.php?threads/aiyima-a07-tpa3255-review-amplifier.18984/
target=_blank rel="noopener noreferrer"
&gt;A07 model&lt;/a&gt; in conjunction with a capable low-ripple psu, I have sold my
&lt;a href=https://www.whathifi.com/nad/c326bee/review
target=_blank rel="noopener noreferrer"
&gt;NAD C326-BEE&lt;/a&gt;. Further signal chain consists of an RPi 3B+ and a
&lt;a href=https://www.hifiberry.com/blog/hifiberry-digi-pro-our-most-advanced-digital-audio-interface/
target=_blank rel="noopener noreferrer"
&gt;Hifiberry Digi&amp;#43; Pro Hat&lt;/a&gt;, both fed by an
&lt;a href=https://ifi-audio.com/products/ipower/
target=_blank rel="noopener noreferrer"
&gt;ifi iPower&lt;/a&gt;, followed by a
&lt;a href=https://fosiaudioshop.com/products/fosi-audio-dac-converter-headphone-amplifier-mini-stereo-pre-amplifier-q5
target=_blank rel="noopener noreferrer"
&gt;Fosi DAC-Q5&lt;/a&gt; into an
&lt;a href=https://fosiaudioshop.com/products/fosi-audio-dac-converter-headphone-amplifier-mini-stereo-pre-amplifier-q5
target=_blank rel="noopener noreferrer"
&gt;FX-Audio Tube-01&lt;/a&gt; Tube Buffer with
&lt;a href=https://www.tubeampdoctor.com/5654w-ge/jan-nos-nib/usa
target=_blank rel="noopener noreferrer"
&gt;General Electric JAN 5654W&lt;/a&gt; new old stock tubes, powered by an
&lt;a href=https://ifi-audio.com/products/ipowerx/
target=_blank rel="noopener noreferrer"
&gt;ifi iPower X&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;While using AirPlay 2 and also listening to
&lt;a href=https://www.radioswissjazz.ch/en/reception/internet
target=_blank rel="noopener noreferrer"
&gt;Radio Swiss Jazz&lt;/a&gt; aacPlus stream on a regular basis, main audio source is Tidal Hifi Plus using
&lt;a href=https://www.hifiberry.com/hifiberryos/
target=_blank rel="noopener noreferrer"
&gt;HifiberryOS&lt;/a&gt; and the
&lt;a href=https://github.com/TonyTromp/tidal-connect-docker
target=_blank rel="noopener noreferrer"
&gt;Tidal Connect Docker&lt;/a&gt; by TonyTromp. Speakers are a pair of
&lt;a href=http://www.audioreview.com/product/speakers/floorstanding-speakers/mission/700.html
target=_blank rel="noopener noreferrer"
&gt;Mission 700&lt;/a&gt; from the 1980s.&lt;/p&gt;
&lt;h3 id="new-amp-combines-it-all"&gt;New Amp Combines it All&lt;/h3&gt;
&lt;p&gt;Some people might consider this signal chain to be extensive. It does sound good enough. The folks at Aiyima apparently found this signal path to be worthwile as well and
&lt;a href=https://invidious.tiekoetter.com/watch?v&amp;#61;xlOWjz5wDfM
target=_blank rel="noopener noreferrer"
&gt;took a few steps&lt;/a&gt; to combine this way of sound processing into one single device: The
&lt;a href=https://www.aiyima.com/products/aiyima-t9-pro
target=_blank rel="noopener noreferrer"
&gt;Aiyima T9 Pro&lt;/a&gt;. It excels. I run it via its USB interface and have another RPi 3B+ hooked to it. Hagen was kind enough to point out
&lt;a href=https://support.hifiberry.com/hc/en-us/community/posts/360012029397/comments/4417139511825
target=_blank rel="noopener noreferrer"
&gt;how to use HifiberryOS with USB audio devices&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Even the great TIDAL Connect Docker works, even in Hi-Res Mode as the T9 Pro indicates 96khz input, where available.&lt;/p&gt;
&lt;p&gt;In my sleeping room, the T9 Pro drives a pair of
&lt;a href=https://picclick.de/Dual-MN-8010-G-junger-Klassiker-274752571567.html
target=_blank rel="noopener noreferrer"
&gt;turquoise DUAL MN 8010 speakers&lt;/a&gt; from the 1990s. They originate from german hypermarket chain Karstadt efforts of stepping into the upper hifi segment.&lt;/p&gt;
&lt;p&gt;The T9 Pro also features Toslink, Coax, Cinch RCA und Bluetooth aptX inputs by the way. In size, it is approximately only a stack of 10 slim cd jewel cases. And its speaker terminals are fairly small. 1,5 mm² is possible. Otherwise use small banana jacks.&lt;/p&gt;
&lt;p&gt;As a side note, if you consider buying the amp, make sure to get the T9 &lt;b&gt;Pro&lt;/b&gt; model.&lt;/p&gt;
&lt;p&gt;I do recommend it to everybody. Thanks Aiyima, we are not affiliated but I am convinced by the performance of this precious Amp. &lt;i&gt;Bang for the Buck&lt;/i&gt;. Now we are talking!&lt;/p&gt;
&lt;h3 id="update-tubes-rolled-for-ge-jan-5654w"&gt;Update: tubes rolled for GE JAN 5654W&lt;/h3&gt;
&lt;p&gt;Replacing the stock 5725 tubes with
&lt;a href=https://www.tubeampdoctor.com/5654w-ge/jan-nos-nib/usa
target=_blank rel="noopener noreferrer"
&gt;GE JAN 5654W&lt;/a&gt; gives additional warmth, softer vocals and more powerful bass punch. &lt;em&gt;Tube rolling&lt;/em&gt; and &lt;em&gt;New Old Stock&lt;/em&gt; are great newly coined terms, too. The process is as easy as 1,2,3, they just sit in a circular 7-pin socket. Try it!&lt;/p&gt;</description></item><item><title>upgrade wifi on hp 17-bs notebook</title><link>https://tilde.town/~kls0e/upgrade-wifi-on-hp-17-bs-notebook/</link><pubDate>Mon, 14 Dec 2020 18:45:16 CET</pubDate><author>Matthias Klose</author><guid>https://tilde.town/~kls0e/upgrade-wifi-on-hp-17-bs-notebook/</guid><description>&lt;p&gt;The HP 17-BS XXXXX series is a widespread budget notebook line, it is somewhat affordable and has mediocre specs one can work with for every-day tasks, whatever that means.&lt;br&gt;
&lt;br&gt;
Its build quality is on the lower edge with lotsa relatively thin plastic involved, but
&lt;a href=https://youtu.be/60HKoSwOkWw?t&amp;#61;262
target=_blank rel="noopener noreferrer"
&gt;here is a video on how to carefully open the device&lt;/a&gt; without breaking it. Thanks, Christian. Cool dialect btw :)&lt;br&gt;
&lt;br&gt;
Turns out it is relatively easy to swap the wifi card. This notebook sports only 1 antenna. The stock
&lt;a href=https://www.realtek.com/en/products/communications-network-ics/item/rtl8723be
target=_blank rel="noopener noreferrer"
&gt;RTL8723BE performs absurdly poor&lt;/a&gt; with its 1x1 2.4 ghz stream. Better buy an
&lt;a href=https://www.ebay.de/sch/i.html?_nkw&amp;#61;RTL8821CE%20combo
target=_blank rel="noopener noreferrer"
&gt;RTL8821CE 802.11AC 1X1 Wi-Fi &amp;#43; BT 4.2 Combo&lt;/a&gt; on ebay or anywhere else for 6€, it is compatible and it is &lt;b&gt;not&lt;/b&gt;
&lt;a href=https://en.wikipedia.org/wiki/Blacklist_%28computing%29#Controversy
target=_blank rel="noopener noreferrer"
&gt;block-listed&lt;/a&gt;
&lt;a href=https://goughlui.com/2014/08/02/laptop-wireless-card-whitelists-an-upgrade-nightmare/
target=_blank rel="noopener noreferrer"
&gt;efi-wise&lt;/a&gt; and delivers beyond stellar in comparison, &lt;em&gt;given&lt;/em&gt; there is an 802.11ac access point around.&lt;/p&gt;</description></item><item><title>add AirPrint to a Brother MFC-7360N</title><link>https://tilde.town/~kls0e/add-airprint-to-a-brother-mfc-7360n/</link><pubDate>Wed, 04 Nov 2020 09:35:18 CET</pubDate><author>Matthias Klose</author><guid>https://tilde.town/~kls0e/add-airprint-to-a-brother-mfc-7360n/</guid><description>&lt;p&gt;With all the recent corona changes, we are in need of a home printer. Of course, b/w laser is the way to go,
&lt;a href=https://www.wired.com/story/why-do-printers-still-suck/
target=_blank rel="noopener noreferrer"
&gt;cheap inkjet printers are a&lt;/a&gt;
&lt;a href=https://pluralistic.net/2020/11/06/horrible-products/#inkwars
target=_blank rel="noopener noreferrer"
&gt;scam&lt;/a&gt; and tend to break right after the warranty period. So I decided to buy a 4-in-1 Brother MFP, the
&lt;a href=https://support.brother.com/g/b/producttop.aspx?c&amp;#61;de&amp;amp;lang&amp;#61;de&amp;amp;prod&amp;#61;mfc7360n_all
target=_blank rel="noopener noreferrer"
&gt;MFC-7360N&lt;/a&gt;. It can print, scan, copy and fax and I got it from eBay kleinanzeigen for 20.- € with an empty toner cartridge.&lt;/p&gt;
&lt;p&gt;I am really happy with this machine and can recommend it to anyone, 3rd party toners are very affordable (around 12.- €) and last for thousands of pages.&lt;/p&gt;
&lt;p&gt;I also like to print scanned developed analog b/w photos with it as they look like photocopies, but that is a matter of taste I guess.&lt;/p&gt;
&lt;p&gt;So the machine works well and performs great, and there is an
&lt;a href=https://apps.apple.com/de/app/brother-iprint-scan/id382775642
target=_blank rel="noopener noreferrer"
&gt;iOS app for it&lt;/a&gt; which allows you to scan to your phone. You can also print with this app but I was looking for
&lt;a href=https://de.wikipedia.org/wiki/AirPrint
target=_blank rel="noopener noreferrer"
&gt;AirPrint&lt;/a&gt; support to integrate directly with iOS devices on the same network. The printer does not feature this relatively new protocol but if you have a linux machine on the network, maybe a raspberry pi,
&lt;a href=https://www.elektronik-kompendium.de/sites/raspberry-pi/2007081.htm
target=_blank rel="noopener noreferrer"
&gt;install CUPS properly&lt;/a&gt; and use the &lt;b&gt;
&lt;a href=https://github.com/pdewacht/brlaser
target=_blank rel="noopener noreferrer"
&gt;brlaser v4 ppd&lt;/a&gt;&lt;/b&gt; with it by Peter De Wachter, thanks! Presto, there&amp;rsquo;s your AirPrint, works like a charm.&lt;/p&gt;
&lt;p&gt;Another reason I recommend Brother is that there is an undocumented &amp;ldquo;toner empty&amp;rdquo;-reset command which will just let you print along until the toner is physically used up:&lt;/p&gt;
&lt;cite&gt;
1. Schalten Sie Ihr Brother MFC Gerät an&lt;br&gt;
2. Öffnen Sie die Frontklappe -lassen Sie den Toner im Drucker&lt;br&gt;
3. Drücken Sie die Taste STORNO 1x -nur einmal&lt;br&gt;
4. Es erscheint die Anzeige "Trommel ersetzen?" 1. JA 2. NEIN -hier nichts auswählen, weiter mit&lt;br&gt;
5. Drücken Sie die STERN-Taste *&lt;br&gt;
6. Drücken Sie die Taste 0 zweimal nacheinander&lt;br&gt;
7. Schließen Sie die Frontklappe&lt;br&gt;
8. fertig&lt;br&gt;
&lt;/cite&gt;
&lt;p&gt;found in a
&lt;a href=https://www.youtube.com/watch?v&amp;#61;kTgTFQ9CGdE
target=_blank rel="noopener noreferrer"
&gt;german video review by VideoP&lt;/a&gt;, thanks.&lt;/p&gt;
&lt;p&gt;So it turns out that the toner cartridge I got with the printer is far from empty and the Brother continues to print just fine :)&lt;/p&gt;</description></item><item><title>Dynamically adjusting bandwidth limits</title><link>https://tilde.town/~kls0e/dynamically-adjusting-bandwidth-limits/</link><pubDate>Sat, 03 Oct 2020 19:47:53 CEST</pubDate><author>Matthias Klose</author><guid>https://tilde.town/~kls0e/dynamically-adjusting-bandwidth-limits/</guid><description>&lt;p&gt;Full quote by Perry:&lt;/p&gt;
&lt;p&gt;&amp;ldquo;I got a request from a refugee center to set up qos so that in the evenings and on weekends the bandwidth-limit gets raised from 35Mbit to 45Mbit, and back down to 35Mbit Mon-Fri 7:00 to 20:00. To do this, I did the following. It&amp;rsquo;s a hack, which could be improved (like storing up slow speed and fast speed in a config file somewhere).&lt;/p&gt;
&lt;p&gt;So this is what I did&amp;hellip;. I wrote the following script (/root/qos-schedule.sh)&lt;/p&gt;
&lt;pre&gt;
#!/bin/sh
. /lib/functions.sh
SLOW=35000
FAST=45000
SPEED=$SLOW
#check to see if it's the weekend
if [[ $(date +%u) -gt 5 ]]; then
SPEED=$FAST
else
# check to see if it's not between 7:00 and 20:00
if [[ $(date +%H) -lt 7 ]] || [[ $(date +%H) -ge 20 ]]; then
SPEED=$FAST
fi
fi
#compare the speed to the current settings.
OLDSPEED=$(uci get qos.ffuplink.download)
if [[ $OLDSPEED -ne $SPEED ]]; then
logger -t qos-schedule.sh "setting time-based qos to $SPEED"
uci set qos.ffuplink.download=$SPEED
uci commit qos
/etc/init.d/qos reload
fi
&lt;/pre&gt;
&lt;p&gt;I added it to crontab to run every hour&lt;/p&gt;
&lt;p&gt;Additionally I added the script to /etc/rc.local (so that the correct speed is set at startup)&lt;/p&gt;
&lt;p&gt;Additionally I created a hotplug script /etc/hotplug.d/ntp/50-qos-schedule&lt;/p&gt;
&lt;pre&gt;
#!/bin/sh
#logger -t hotplug-ntp-qos-schedule "Action is $ACTION, stratum is $stratum"
[ "$ACTION" = stratum ] || exit 0
logger -t hotplug-ntp-qos-schedule "Running qos-schedule from ntp hotplug event"
/root/qos-schedule.sh
&lt;/pre&gt;
&lt;p&gt;Lastly, I added &lt;code&gt;/root/qos-schedule.sh&lt;/code&gt; and &lt;code&gt;/etc/hotplug.d/ntp/50-qos-schedule&lt;/code&gt; to &lt;code&gt;/etc/sysupgrade.conf&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;kls0e Would you like to add that to your list of super cool tutorials?&amp;rdquo;&lt;/p&gt;
&lt;p&gt;&lt;i&gt;absolutely.&lt;/i&gt;&lt;/p&gt;</description></item><item><title>iPerf on Fritzbox stock firmware</title><link>https://tilde.town/~kls0e/iperf-on-fritzbox-stock-firmware/</link><pubDate>Fri, 02 Oct 2020 19:19:00 CEST</pubDate><author>Matthias Klose</author><guid>https://tilde.town/~kls0e/iperf-on-fritzbox-stock-firmware/</guid><description>&lt;p&gt;iPerf is a great cli tool to measure raw network throughput between two devices. I often
&lt;a href=https://tilde.town/~kls0e/measuring-network-throughput-between-two-nodes-with-iperf/
&gt;use it with OpenWrt&lt;/a&gt; and while talking about AVMs Fritzbox routers during a Freifunk Berlin meeting, Nicolas mentioned that there is a little-known support interface with their stock firmware at
&lt;a href=https://fritz.box/support.lua
target=_blank rel="noopener noreferrer"
&gt;https://fritz.box/support.lua&lt;/a&gt; that offers starting an iPerf server – look for &lt;code&gt;Durchsatzmessungen&lt;/code&gt;. Very handy.&lt;br&gt;Note that on client side, parameter &lt;code&gt;-p 4711&lt;/code&gt; is required for the non-standard port.&lt;/p&gt;</description></item><item><title>How to host a (small) website on your Freifunk node</title><link>https://tilde.town/~kls0e/how-to-host-a-small-website-on-your-freifunk-node/</link><pubDate>Sun, 23 Aug 2020 08:07:49 CEST</pubDate><author>Matthias Klose</author><guid>https://tilde.town/~kls0e/how-to-host-a-small-website-on-your-freifunk-node/</guid><description>&lt;p&gt;Due to the versatility of OpenWrt, being a fully equipped Linux distribution itself, there are all tools already installed on your Freifunk node that are required turn it into a small web server. In fact its web gui LuCI is a website served by the node already. I suggest starting with
&lt;a href=https://en.wikipedia.org/wiki/Static_web_page
target=_blank rel="noopener noreferrer"
&gt;static html&amp;#43;css&lt;/a&gt; and I can recommend
&lt;a href=https://gohugo.io/
target=_blank rel="noopener noreferrer"
&gt;Hugo&lt;/a&gt; as a Framework to generate it. So let&amp;rsquo;s use the existing infrastructure to host our own website:&lt;/p&gt;
&lt;h3 id="selecting-the-best-storage-option-for-your-webroot"&gt;selecting the best storage option for your webroot&lt;/h3&gt;
&lt;ol start="0"&gt;
&lt;li&gt;
&lt;p&gt;First think about where you would like to store your web files aka your &lt;i&gt;webroot&lt;/i&gt;. This can be anywhere accessible by your nodes&amp;rsquo; file system. Essentially, we have three options: ramdisk, external usb storage and router flash memory.&lt;br&gt;
Check out your options and the free available space with its corresponding
&lt;a href=https://de.wikipedia.org/wiki/Einh%C3%A4ngepunkt
target=_blank rel="noopener noreferrer"
&gt;mount points&lt;/a&gt; with &lt;code&gt;df -h&lt;/code&gt;.&lt;br&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;I decided to use the routers&amp;rsquo; ramdisk, already available at /tmp using tmpfs. If you have a device with at least 128 MB of RAM and your website is not too large, I think this is a good way to go since ram is blazing fast and tmpfs size is configured as 50% spare by default, i.e. it grows with the content it holds, up to a size of 64 MB. Rest of RAM is available to the system.&lt;br&gt;
As we know, RAM gets zapped on every boot so I set up a small script to run with
&lt;a href=https://www.raspberrypi.org/documentation/linux/usage/rc-local.md
target=_blank rel="noopener noreferrer"
&gt;/etc/rc.local&lt;/a&gt;. The script pulls a
&lt;a href=http://computing.help.inf.ed.ac.uk/FAQ/whats-tarball-or-how-do-i-unpack-or-create-tgz-or-targz-file
target=_blank rel="noopener noreferrer"
&gt;tarball&lt;/a&gt; using
&lt;a href=https://www.gnu.org/software/wget/
target=_blank rel="noopener noreferrer"
&gt;wget&lt;/a&gt; every time the router boots and extracts it to &lt;code&gt;/tmp/wwwroot&lt;/code&gt; which is the path to my webroot.&lt;br&gt;
Of course you can also
&lt;a href=https://openwrt.org/docs/guide-user/storage/usb-drives
target=_blank rel="noopener noreferrer"
&gt;mount external usb storage&lt;/a&gt; and use it as a webroot, it will be slower than tmpfs but persistent. Although technically possible, I would not use the routers&amp;rsquo; own flash chips to store a webroot if the device has
&lt;a href=https://en.wikipedia.org/wiki/Flash_memory#NOR_memories
target=_blank rel="noopener noreferrer"
&gt;NOR flash&lt;/a&gt; because there might be no error correction and the blocks wear out after a certain amount of write cycles.&lt;br&gt;
If your device sports a
&lt;a href=https://en.wikipedia.org/wiki/Flash_memory#NAND_memories
target=_blank rel="noopener noreferrer"
&gt;NAND flash chip&lt;/a&gt;, such as the
&lt;a href=https://openwrt.org/toh/avm/fritz.box.wlan.3370
target=_blank rel="noopener noreferrer"
&gt;Fritzbox 3370&lt;/a&gt; or the
&lt;a href=https://openwrt.org/toh/gl.inet/gl.inet_gl-ar300m
target=_blank rel="noopener noreferrer"
&gt;GL.iNet AR-300-M&lt;/a&gt;, using NAND as webroot is fine.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id="configuring-the-webserver"&gt;configuring the webserver&lt;/h3&gt;
&lt;ol start="2"&gt;
&lt;li&gt;Let&amp;rsquo;s use uhttpd, which already serves LuCI on your freifunk node, as the webserver. It actually performs very well. Edit the config at &lt;code&gt;/etc/config/uhttpd&lt;/code&gt;:&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Just copy the &lt;code&gt;config uhttpd &amp;lsquo;main&amp;rsquo;&lt;/code&gt; block, insert it at the end of the file and rename the new section. also adjust the path at &lt;code&gt;home&lt;/code&gt; to your webroot, raise &lt;code&gt;max_requests&lt;/code&gt; and &lt;code&gt;max_connections&lt;/code&gt; a bit and, if you want to leave LuCI enabled, choose some other ports for the &lt;code&gt;listen_http&lt;/code&gt; and &lt;code&gt;listen_https&lt;/code&gt; entries.&lt;br&gt;
Here&amp;rsquo;s my sample config:&lt;/p&gt;
&lt;pre&gt;config uhttpd 'kls0e'
list listen_http '0.0.0.0:10178'
list listen_http '[::]:10178'
list listen_https '0.0.0.0:10785'
list listen_https '[::]:10785'
option home '/tmp/wwwroot'
option max_requests '48'
option max_connections '400'
option cert '/etc/uhttpd.crt'
option key '/etc/uhttpd.key'
option cgi_prefix '/cgi-bin'
list lua_prefix '/cgi-bin/luci=/usr/lib/lua/luci/sgi/uhttpd.lua'
option script_timeout '60'
option network_timeout '30'
option http_keepalive '20'
option tcp_keepalive '1'
option ubus_prefix '/ubus'
option rfc1918_filter '0'
option redirect_https '0'&lt;/pre&gt;&lt;br&gt;
&lt;p&gt;As you can see, this website is served at port 10178. Maybe you can guess from looking at the config that I am planning to get proper SSL running as well, so the website is reachable using https:// without certificate warnings, as every website should be, to avoid
&lt;a href=https://en.wikipedia.org/wiki/Man-in-the-middle_attack
target=_blank rel="noopener noreferrer"
&gt;plain-text MitM manipulation&lt;/a&gt;.&lt;br&gt;
There is a tutorial on how to do that:
&lt;a href=https://stokito.wordpress.com/2017/10/16/how-to-letsencrypt-https-on-openwrt-with-uhttpd/
target=_blank rel="noopener noreferrer"
&gt;LE-HTTPS on OpenWRT with uhttpd by stokito&lt;/a&gt;&lt;br&gt;
If you have an idea on how to fully automate the Let&amp;rsquo;s Encrypt certbot renewal process directly on an OpenWrt device, please let me know.&lt;/p&gt;</description></item><item><title>manually downgrading mesh links using olsr Link Quality Multipliers (LinkQM)</title><link>https://tilde.town/~kls0e/manually-downgrading-mesh-links-using-olsr-link-quality-multipliers-linkqm/</link><pubDate>Wed, 19 Aug 2020 06:59:26 CEST</pubDate><author>Matthias Klose</author><guid>https://tilde.town/~kls0e/manually-downgrading-mesh-links-using-olsr-link-quality-multipliers-linkqm/</guid><description>&lt;p&gt;Let&amp;rsquo;s say you have a node that sees two other nodes in the mesh with a fairly good connection but you want to shape the traffic flow because there are reasons to do so.
To achieve this, we can apply link quality multipliers to certain routes. The router will multiply the routes&amp;rsquo; priority by this factor and thus downgrade it if we choose a number lower than 1, e.g. 0.5.&lt;/p&gt;
&lt;ol start="0"&gt;
&lt;li&gt;
&lt;p&gt;Find out which ip your mesh interface and the other nodes&amp;rsquo; mesh interface has:&lt;br&gt;
&lt;code&gt;neigh.sh&lt;/code&gt;&lt;br&gt;The left column consists of mesh interface ips of the router you are logged into, the right column consists of remote mesh interface ips. If you are lucky, a third column to the right will appear, showing the remote olsr hostnames.&lt;br&gt;
Note your mesh ip and the remote mesh ip.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;You need to know your local mesh interface name you want to apply the LinkQM rule to. In the following command, insert your mesh ip (left column from step 0) that sees the other node you want to downgrade the link to:&lt;br&gt;
&lt;code&gt;ip a | grep &amp;lt;your mesh ip from neigh.sh&amp;gt;&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The last phrase of the output is the local interface mesh name, common results include wlan0-mesh-2, wlan1-adhoc-5, fflan, &amp;hellip; - depending on your setup.&lt;br&gt;
Important: If it is a wireless mesh interface, we need to know the network name assigned to it. You can find this information in &lt;code&gt;/etc/config/wireless&lt;/code&gt;. Everything that starts with wlan0- is most probably &lt;code&gt;wireless0&lt;/code&gt;, everything that starts with wlan1- is most probably &lt;code&gt;wireless1&lt;/code&gt;.&lt;/p&gt;
&lt;ol start="2"&gt;
&lt;li&gt;With this information, we can edit the olsr config at &lt;code&gt;/etc/config/olsrd&lt;/code&gt;. We need to scroll down to the appropriate interface section with the local mesh interface network name we have found out in step 1.&lt;br&gt;
We can now add a new option for it:&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;code&gt;option LinkQualityMult &amp;lsquo;&amp;lt;remote mesh ip&amp;gt; &amp;lt;multiplier&amp;gt;&amp;rsquo;&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;So as an example, my result for step 1 was &lt;code&gt;fflan&lt;/code&gt;, because the link I want to downgrade is reachable through a lan mesh interface. The remote mesh ip from step 0 in my example is &lt;code&gt;10.230.23.158&lt;/code&gt; and I would like to downgrade the link by factor 2, so my multiplier is 0.5.
So I am scrolling down to where the olsr config says &lt;code&gt;option interface &amp;lsquo;fflan&amp;rsquo;&lt;/code&gt; and I add the corresponding LinkQM option like so:&lt;/p&gt;
&lt;pre&gt;config Interface
option ignore '0'
option interface 'fflan'
option Mode 'ether'
option LinkQualityMult '10.230.23.158 0.5'&lt;/pre&gt;
&lt;p&gt;Keep in mind, for wireless mesh interfaces, you need to look for wireless0 or wireless1 instead of fflan. To apply the changes, restart olsrd by issuing &lt;code&gt;/etc/init.d/olsrd restart&lt;/code&gt;. Wait some minutes before testing the new routing until the router reworks its routing priorities.&lt;br&gt;&lt;br&gt;Freifunk Berlin can be complicated sometimes but you can learn a lot.&lt;/p&gt;</description></item><item><title>Measuring network throughput between two nodes with iPerf</title><link>https://tilde.town/~kls0e/measuring-network-throughput-between-two-nodes-with-iperf/</link><pubDate>Tue, 18 Aug 2020 08:11:42 CEST</pubDate><author>Matthias Klose</author><guid>https://tilde.town/~kls0e/measuring-network-throughput-between-two-nodes-with-iperf/</guid><description>&lt;p&gt;In mesh networks, throughput between nodes that are supposed to talk to each other is the key performance factor. There is a handy tool called iPerf already pre-installed with our Freifunk images that can measure the actual bandwidth between two nodes. It is client-server based, so to benchmark, run&lt;br&gt;
&lt;code&gt;iperf -s&lt;/code&gt; on one of the nodes, this will start an iperf server listening to connections, and on the second node, run&lt;br&gt;
&lt;code&gt;iperf -c &amp;lt;server&amp;gt; -d -t 15 -i 1 -P 2&lt;/code&gt;, this will connect the iperf client to the server and will run a heavy raw network throughput benchmark for 15 seconds. It is always good to play around with the options a bit to get the idea and maybe fine-tune the command.&lt;br&gt;
If you are about to reposition an antenna and would like to check out a continuous iperf feed while doing so, I suggest using&lt;br&gt;
&lt;code&gt;iperf -c &amp;lt;server&amp;gt; -d -t 360 -i 1&lt;/code&gt;, this will be a bit easier to lift for the tcp stack of the device.&lt;br&gt;&lt;br&gt;
By the way, there is
&lt;a href=https://iperf.fr/
target=_blank rel="noopener noreferrer"
&gt;iPerf3&lt;/a&gt;, which has been around for a couple of years now, and I hope we will include it in our future Freifunk images, so have a look at this newer version, too.&lt;/p&gt;</description></item><item><title>How to run your own Freifunk Berlin service</title><link>https://tilde.town/~kls0e/how-to-run-your-own-freifunk-berlin-service/</link><pubDate>Thu, 13 Aug 2020 20:25:03 CEST</pubDate><author>Matthias Klose</author><guid>https://tilde.town/~kls0e/how-to-run-your-own-freifunk-berlin-service/</guid><description>&lt;p&gt;Martin, a fellow Freifunker from
&lt;a href=https://fuerstenwalde.freifunk.net/
target=_blank rel="noopener noreferrer"
&gt;Freifunk Fürstenwalde&lt;/a&gt; asked me to publish a tutorial on how to set up your own service in the Freifunk Berlin network. It is actually quite easy once you know how.&lt;/p&gt;
&lt;p&gt;Note: Your service has to have an ip reachable from within Freifunk Berlin, you can configure a static ip for it from your Freifunk dhcp pool. If you use the first dhcp ip your Freifunk router would issue to a client, edit its &lt;code&gt;/etc/config/dhcp&lt;/code&gt; and raise &lt;code&gt;option start &amp;lsquo;2&amp;rsquo;&lt;/code&gt; to &lt;code&gt;option start &amp;lsquo;3&amp;rsquo;&lt;/code&gt; in the lower dhcp config block. This reserves the ip for your static configured service host and avoids duplicate ip delegation via dhcp.&lt;/p&gt;
&lt;p&gt;How to announce your service in the Freifunk Berlin network:&lt;/p&gt;
&lt;ol start="0"&gt;
&lt;li&gt;
&lt;p&gt;log in to your router via ssh&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;edit the olsr config of your router:&lt;br&gt;
&lt;code&gt;vim /etc/config/olsrd&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;scroll down to the section that starts with&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;config LoadPlugin
option library 'olsrd_nameservice'&lt;/pre&gt;
&lt;ol start="3"&gt;
&lt;li&gt;
&lt;p&gt;press i to insert text&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;add&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;list hosts '&amp;lt;freifunk-ip&amp;gt; &amp;lt;hostname without .olsr&amp;gt;'
list service 'http://&amp;lt;freifunk-IP:port&amp;gt;|tcp|&amp;lt;name of service&amp;gt;'&lt;/pre&gt;
&lt;p&gt;example: let&amp;rsquo;s say my service has &lt;code&gt;10.31.44.119&lt;/code&gt; and I would like the host to be known as &lt;code&gt;kls0e.olsr&lt;/code&gt;, and let&amp;rsquo;s say there is a web server up at port &lt;code&gt;10178&lt;/code&gt;, and every router at Freifunk Berlin should show a link named &lt;code&gt;kls0e&lt;/code&gt; to this server under &lt;code&gt;Services&lt;/code&gt; on its web gui, then my nameservice plugin section would look like so:
&lt;br&gt;&lt;/p&gt;
&lt;pre&gt;config LoadPlugin
option library 'olsrd_nameservice'
option suffix '.olsr'
option hosts_file '/tmp/hosts/olsr'
option latlon_file '/var/run/latlon.js'
option services_file '/var/etc/services.olsr'
list hosts '10.31.44.119 kls0e'
list service 'http://10.31.44.119:10178|tcp|kls0e'&lt;/pre&gt;&lt;br&gt;
&lt;p&gt;&lt;code&gt;udp&lt;/code&gt; protocol is also an option instead of &lt;code&gt;tcp&lt;/code&gt;. Port info is mandatory, even if it is port 80.&lt;br&gt;&lt;br&gt;
Give it 5-15 minutes to &lt;i&gt;propagate&lt;/i&gt; your service announcement across the network.&lt;br&gt;&lt;br&gt;
From my experience, sometimes not all nodes can resolve the hosts entry, so I have chosen to publish the service with its direct Freifunk ip instead of an OLSR host name. The latter would be cooler but
&lt;a href=https://de.wikipedia.org/wiki/Form_follows_function
target=_blank rel="noopener noreferrer"
&gt;form follows function&lt;/a&gt;, I am glad if it works for you tho, please try.&lt;br&gt;
Update: Noki kindly mentioned that it makes sense to issue a
&lt;a href=https://openwrt.org/docs/guide-user/base-system/dhcp_configuration
target=_blank rel="noopener noreferrer"
&gt;static dhcp lease&lt;/a&gt; for the device hosting the service.&lt;/p&gt;</description></item><item><title>Freifunk Berlin mesh protocol snippets</title><link>https://tilde.town/~kls0e/freifunk-berlin-mesh-protocol-snippets/</link><pubDate>Thu, 13 Aug 2020 07:44:04 CEST</pubDate><author>Matthias Klose</author><guid>https://tilde.town/~kls0e/freifunk-berlin-mesh-protocol-snippets/</guid><description>&lt;p&gt;We are moving from
&lt;a href=https://www.wiesbaden.freifunk.net/2017/05/23/umstellung-ibss-auf-80211s.html
target=_blank rel="noopener noreferrer"
&gt;IBSS to 802.11s&lt;/a&gt; as the main mesh protocol used for our meshes in the
&lt;a href=https://hopglass.berlin.freifunk.net/
target=_blank rel="noopener noreferrer"
&gt;Freifunk Berlin network&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;I think the easiest way to adjust the mesh protocol of a freifunk router is to edit &lt;code&gt;/etc/config/wireless&lt;/code&gt; and insert or replace the config blocks accordingly.&lt;/p&gt;
&lt;p&gt;Please pay attention to the interface names of dual-band devices, the majority of routers call their 2.4 ghz module &lt;code&gt;radio0&lt;/code&gt; and their 5 ghz module &lt;code&gt;radio1&lt;/code&gt;, but a
&lt;a href=https://openwrt.org/toh/ubiquiti/unifi_appro
target=_blank rel="noopener noreferrer"
&gt;Ubiquiti UAP Pro&lt;/a&gt; flying saucer frisbee for that matter keeps it vice versa.&lt;/p&gt;
&lt;p&gt;If your router only has one radio (radio0), just use the radio0 snippet.&lt;/p&gt;
&lt;pre&gt;
# 802.11s for 2.4 ghz
config wifi-iface
option network 'wireless0'
option device 'radio0'
option ifname 'wlan0-mesh-2'
option mode 'mesh'
option encryption 'none'
option mesh_fwding '0'
option mesh_id 'Mesh-Freifunk-Berlin'
option mcast_rate '12000'
# 802.11s for 5 ghz
config wifi-iface
option network 'wireless1'
option device 'radio1'
option ifname 'wlan1-mesh-5'
option mode 'mesh'
option encryption 'none'
option mesh_fwding '0'
option mesh_id 'Mesh-Freifunk-Berlin'
option mcast_rate '12000'&lt;/pre&gt;
&lt;pre&gt;
# Ad-Hoc / IBSS for 2.4 ghz
config wifi-iface
option ssid 'intern-ch13.freifunk.net'
option ifname 'wlan0-adhoc-2'
option network 'wireless0'
option encryption 'none'
option device 'radio0'
option bssid 'D2:CA:FF:EE:BA:BE'
option mcast_rate '6000'
option mode 'adhoc'
# Ad-Hoc / IBSS for 5 ghz
config wifi-iface
option bssid '02:36:CA:FF:EE:EE'
option ifname 'wlan1-adhoc-5'
option network 'wireless1'
option ssid 'intern-ch36.freifunk.net'
option mcast_rate '12000'
option mode 'adhoc'
option device 'radio1'&lt;/pre&gt;&lt;br&gt;
&lt;p&gt;You can apply all changes made to &lt;code&gt;/etc/config/wireless&lt;/code&gt; simply by issuing &lt;code&gt;wifi&lt;/code&gt;. There is no need to reboot.
If you want the new set mesh interfaces to be monitored with the stats monitor, edit &lt;code&gt;/etc/config/luci_statistics&lt;/code&gt; and replace or adjust the interface names in the corresponding sections. To apply these changes, run &lt;code&gt;/etc/init.d/luci_statistics restart&lt;/code&gt;.&lt;br&gt;
Bonus: If you have made changes to &lt;code&gt;/etc/config/network&lt;/code&gt;, just run &lt;code&gt;service network reload&lt;/code&gt; to apply.&lt;/p&gt;</description></item><item><title>24h auto rebooting a router with cron</title><link>https://tilde.town/~kls0e/24h-auto-rebooting-a-router-with-cron/</link><pubDate>Sat, 08 Aug 2020 08:23:42 CEST</pubDate><author>Matthias Klose</author><guid>https://tilde.town/~kls0e/24h-auto-rebooting-a-router-with-cron/</guid><description>&lt;p&gt;There is an old
&lt;a href=https://openwrt.org/toh/tp-link/tl-wdr3600
target=_blank rel="noopener noreferrer"
&gt;WDR3600&lt;/a&gt; we have had donated by some kind soul and its 2.4 ghz radio every now and then would conk out, but it is just not broken enough to throw it away. Rebooting it every 24 hours does the trick:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;crontab -e&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;press &lt;code&gt;shift+G&lt;/code&gt; to go to the end of the cron job table&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;press &lt;code&gt;i&lt;/code&gt; for text insert mode&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;insert this line to reboot the router every night at 3:42 AM:&lt;br&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;code&gt;42 3 * * * sleep 70 &amp;amp;&amp;amp; touch /etc/banner &amp;amp;&amp;amp; reboot &amp;gt;/dev/null 2&amp;gt;&amp;amp;1&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;This was initially posted by
&lt;a href=https://www.freifunk-winterberg.net/auto-reboot-von-freifunk-router-per-cron-job/
target=_blank rel="noopener noreferrer"
&gt;Jan&lt;/a&gt; at Freifunk Winterberg and I thought the line is worth sharing as there is a simple fail-safe option included which essentially prevents the router from accidentally entering a continuous boot-loop caused by a wrong date / time set on the machine. This is a common issue since most routers do not sport an embedded buffered clock and sync their time and date by
&lt;a href=https://openwrt.org/docs/guide-user/advanced/ntp_configuration
target=_blank rel="noopener noreferrer"
&gt;ntp&lt;/a&gt; every once in a while.&lt;/p&gt;</description></item><item><title>compiling Falter Images for Freifunk Berlin</title><link>https://tilde.town/~kls0e/compiling-falter-images-for-freifunk-berlin/</link><pubDate>Sun, 02 Aug 2020 15:24:52 CEST</pubDate><author>Matthias Klose</author><guid>https://tilde.town/~kls0e/compiling-falter-images-for-freifunk-berlin/</guid><description>&lt;p&gt;For various reasons, I am glad that Nick has made some efforts to flange our precious Freifunk Berlin Firmware onto Vanilla OpenWrt. The results are in the
&lt;a href=https://github.com/Freifunk-Spalter/
target=_blank rel="noopener noreferrer"
&gt;Falter Github Repo&lt;/a&gt;.&lt;/p&gt;
&lt;hr&gt;
&lt;b&gt;Update&lt;/b&gt;: The following tutorial is now deprecated as it might contain an outdated diffconfig. Things just got easier: Martin was so kind as to create a &lt;a href="https://github.com/Freifunk-Spalter/falter-firmware-builder#tutorial-build-a-stable-release" target=_blank&gt;build script for Falter images, please find it here&lt;/a&gt; and use it instead.
&lt;hr&gt;
Here is a quick tutorial on how to build working Freifunk Berlin tunneldigger Images using Falter on a linux box:
&lt;ol start="0"&gt;
&lt;li&gt;
&lt;p&gt;install OpenWrt build prerequisites aka needed packages as described
&lt;a href=https://openwrt.org/docs/guide-developer/build-system/install-buildsystem#examples_of_package_installations
target=_blank rel="noopener noreferrer"
&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;clone OpenWrt&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;code&gt;git clone
&lt;a href=https://git.openwrt.org/openwrt/openwrt.git
target=_blank rel="noopener noreferrer"
&gt;https://git.openwrt.org/openwrt/openwrt.git&lt;/a&gt; falter&lt;/code&gt;&lt;/p&gt;
&lt;ol start="2"&gt;
&lt;li&gt;change into falter folder by&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;code&gt;cd falter&lt;/code&gt;&lt;/p&gt;
&lt;ol start="3"&gt;
&lt;li&gt;copy feeds.conf.default to feeds.conf&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;code&gt;cp feeds.conf.default feeds.conf&lt;/code&gt;&lt;/p&gt;
&lt;ol start="4"&gt;
&lt;li&gt;add the following line to feeds.conf&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;code&gt;src-git falter
&lt;a href=https://github.com/Freifunk-Spalter/packages.git
target=_blank rel="noopener noreferrer"
&gt;https://github.com/Freifunk-Spalter/packages.git&lt;/a&gt;&lt;/code&gt;&lt;/p&gt;
&lt;ol start="5"&gt;
&lt;li&gt;run the following two scripts&lt;/li&gt;
&lt;/ol&gt;
&lt;code&gt;
./scripts/feeds update -a&lt;br&gt;
./scripts/feeds install -a
&lt;/code&gt;
&lt;br&gt;&lt;br&gt;
&lt;ol start="6"&gt;
&lt;li&gt;select your platform and router with the following command. if you do not know where to find it, have a look at the OpenWrt wiki (I&amp;rsquo;d just google openwrt &amp;lt;routername&amp;gt;) for the router you want to build for, and look at the URLs of the downloadable images, you can get the info from the path, e.g. &amp;ldquo;ath79&amp;rdquo; and such&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;code&gt;make menuconfig&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;save + exit.&lt;/p&gt;
&lt;ol start="7"&gt;
&lt;li&gt;create a plain-text file called &lt;code&gt;diffconfig&lt;/code&gt; and insert this with your favourite text editor:&lt;br&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;
CONFIG_PACKAGE_cgi-io=y
CONFIG_PACKAGE_collectd=y
CONFIG_PACKAGE_collectd-mod-cpu=y
CONFIG_PACKAGE_collectd-mod-interface=y
CONFIG_PACKAGE_collectd-mod-iwinfo=y
CONFIG_PACKAGE_collectd-mod-load=y
CONFIG_PACKAGE_collectd-mod-memory=y
CONFIG_PACKAGE_collectd-mod-network=y
CONFIG_PACKAGE_collectd-mod-olsrd=y
CONFIG_PACKAGE_collectd-mod-ping=y
CONFIG_PACKAGE_collectd-mod-rrdtool=y
CONFIG_PACKAGE_collectd-mod-uptime=y
CONFIG_PACKAGE_falter-berlin-dhcp-defaults=y
CONFIG_PACKAGE_falter-berlin-firewall-defaults=y
CONFIG_PACKAGE_falter-berlin-freifunk-defaults=y
CONFIG_PACKAGE_falter-berlin-lib-guard=y
CONFIG_PACKAGE_falter-berlin-migration=y
CONFIG_PACKAGE_falter-berlin-network-defaults=y
CONFIG_PACKAGE_falter-berlin-olsrd-defaults=y
CONFIG_PACKAGE_falter-berlin-statistics-defaults=y
CONFIG_PACKAGE_falter-berlin-system-defaults=y
CONFIG_PACKAGE_falter-berlin-tunneldigger=y
CONFIG_PACKAGE_falter-berlin-uhttpd-defaults=y
CONFIG_PACKAGE_falter-berlin-uplink-tunnelberlin-tunneldigger=y
CONFIG_PACKAGE_falter-common=y
CONFIG_PACKAGE_falter-common-olsr=y
CONFIG_PACKAGE_falter-policyrouting=y
CONFIG_PACKAGE_falter-profiles=y
CONFIG_PACKAGE_ip-full=y
CONFIG_PACKAGE_iperf=y
CONFIG_PACKAGE_kmod-ipip=y
CONFIG_PACKAGE_kmod-iptunnel=y
CONFIG_PACKAGE_kmod-iptunnel4=y
CONFIG_PACKAGE_kmod-l2tp=y
CONFIG_PACKAGE_kmod-l2tp-eth=y
CONFIG_PACKAGE_kmod-l2tp-ip=y
CONFIG_PACKAGE_kmod-ledtrig-default-on=y
CONFIG_PACKAGE_kmod-ledtrig-heartbeat=y
CONFIG_PACKAGE_kmod-ledtrig-netdev=y
CONFIG_PACKAGE_kmod-ledtrig-timer=y
CONFIG_PACKAGE_kmod-udptunnel4=y
CONFIG_PACKAGE_kmod-udptunnel6=y
CONFIG_PACKAGE_libelf=y
CONFIG_PACKAGE_libiwinfo-lua=y
CONFIG_PACKAGE_libltdl=y
CONFIG_PACKAGE_liblua=y
CONFIG_PACKAGE_liblucihttp=y
CONFIG_PACKAGE_libuhttpd-openssl=y
CONFIG_PACKAGE_liblucihttp-lua=y
CONFIG_PACKAGE_libncurses=y
CONFIG_PACKAGE_liboping=y
CONFIG_PACKAGE_libpcap=y
CONFIG_PACKAGE_librrd1=y
CONFIG_PACKAGE_librt=y
CONFIG_PACKAGE_libubus-lua=y
CONFIG_PACKAGE_libuci-lua=y
CONFIG_PACKAGE_lua=y
CONFIG_PACKAGE_luci-app-falter-owm=y
CONFIG_PACKAGE_luci-app-falter-owm-ant=y
CONFIG_PACKAGE_luci-app-falter-owm-cmd=y
CONFIG_PACKAGE_luci-app-falter-owm-gui=y
CONFIG_PACKAGE_luci-app-ffwizard-falter=y
CONFIG_PACKAGE_luci-app-firewall=y
CONFIG_PACKAGE_luci-app-olsr=y
CONFIG_PACKAGE_luci-app-olsr-services=y
CONFIG_PACKAGE_luci-app-opkg=y
CONFIG_PACKAGE_luci-app-statistics=y
CONFIG_PACKAGE_luci-base=y
CONFIG_PACKAGE_luci-compat=y
CONFIG_PACKAGE_luci-lib-base=y
CONFIG_PACKAGE_luci-lib-httpclient=y
CONFIG_PACKAGE_luci-lib-httpprotoutils=y
CONFIG_PACKAGE_luci-lib-ip=y
CONFIG_PACKAGE_luci-lib-ipkg=y
CONFIG_PACKAGE_luci-lib-json=y
CONFIG_PACKAGE_luci-lib-jsonc=y
CONFIG_PACKAGE_luci-lib-nixio=y
CONFIG_PACKAGE_luci-ssl-openssl=y
CONFIG_PACKAGE_luci-mod-admin-full=y
CONFIG_PACKAGE_luci-mod-falter=y
CONFIG_PACKAGE_luci-mod-network=y
CONFIG_PACKAGE_luci-mod-status=y
CONFIG_PACKAGE_luci-mod-system=y
CONFIG_PACKAGE_luci-theme-bootstrap=y
CONFIG_PACKAGE_micrond=y
CONFIG_PACKAGE_mtr=y
CONFIG_PACKAGE_olsrd=y
CONFIG_PACKAGE_olsrd-mod-arprefresh=y
CONFIG_PACKAGE_olsrd-mod-dyn-gw=y
CONFIG_PACKAGE_olsrd-mod-jsoninfo=y
CONFIG_PACKAGE_olsrd-mod-nameservice=y
CONFIG_PACKAGE_olsrd-mod-txtinfo=y
CONFIG_PACKAGE_olsrd-mod-watchdog=y
CONFIG_PACKAGE_pingcheck=y
CONFIG_PACKAGE_resolveip=y
CONFIG_PACKAGE_rpcd=y
CONFIG_PACKAGE_rpcd-mod-file=y
CONFIG_PACKAGE_rpcd-mod-iwinfo=y
CONFIG_PACKAGE_rpcd-mod-luci=y
CONFIG_PACKAGE_rrdtool1=y
CONFIG_PACKAGE_tcpdump=y
CONFIG_PACKAGE_terminfo=y
CONFIG_PACKAGE_uclibcxx=y
CONFIG_PACKAGE_uhttpd=y
CONFIG_PACKAGE_uhttpd-mod-ubus=y
CONFIG_PACKAGE_zlib=y
&lt;/pre&gt;
&lt;p&gt;The diffconfig essentially tells the OpenWrt build system to include our Freifunk Berlin packages as well&lt;/p&gt;
&lt;ol start="8"&gt;
&lt;li&gt;
&lt;p&gt;merge the diffconfig into the OpenWrt .config file&lt;br&gt;
&lt;code&gt;cat diffconfig &amp;gt;&amp;gt; .config&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Sync the edited OpenWrt config, important, don&amp;rsquo;t forget:&lt;br&gt;
&lt;code&gt;make defconfig&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;finally: Build the whole thing. The first build will take some time, roughly 1h or so, after that the consecutive builds will be much faster&lt;br&gt;
&lt;code&gt;make -jX V=s&lt;/code&gt;
important: replace the X with cpu threads of your machine +1, for example I have 4 cpu cores that can do 8 threads via Hyperthreading, so I compile using &lt;code&gt;make -j9 V=s&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The resulting image will be in bin/targets and is ready to flash. Of course it can be used for plain-mesh nodes as well.
You can also sysupgrade from 18.06-based and 19.07-based nodes preserving their config.&lt;/p&gt;
&lt;p&gt;Enjoy your falters! Many thanks to everyone who contributed to this and to the Freifunk Berlin Firmware.&lt;/p&gt;
&lt;p&gt;PS: Whenever you want to update your buildroot with the latest patches and code, run&lt;br&gt;
&lt;code&gt;git pull&lt;br&gt;
./scripts/feeds update -a&lt;br&gt;
./scripts/feeds install -a&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;and continue from step 6.&lt;/p&gt;</description></item><item><title>speedtest oneliner for OpenWrt</title><link>https://tilde.town/~kls0e/speedtest-oneliner-for-openwrt/</link><pubDate>Tue, 21 Jul 2020 06:47:25 CEST</pubDate><author>Matthias Klose</author><guid>https://tilde.town/~kls0e/speedtest-oneliner-for-openwrt/</guid><description>&lt;p&gt;rudimentary OpenWrt speed test to /dev/null, 100 MB:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;START=$(date +%s); wget -O /dev/null http://speed.hetzner.de/100MB.bin; END=$(date +%s); echo $((END-START))| awk &amp;#39;{print int(800/$1)}&amp;#39;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Mbit/s is shown after completion. Should work on other Linuxes, too, needs wget.&lt;/p&gt;
&lt;p&gt;Sourced from the
&lt;a href=https://forum.freifunk.net/t/speedtest-per-konsole/9514/6
target=_blank rel="noopener noreferrer"
&gt;freifunk forum&lt;/a&gt;, posted by adorfer, there are some elaborate alternatives mentioned in the thread but this simple approach works for me.&lt;/p&gt;</description></item><item><title>FahrArt bike repair at Haus der Materialisierung</title><link>https://tilde.town/~kls0e/fahrart-bike-repair-at-haus-der-materialisierung/</link><pubDate>Mon, 20 Jul 2020 14:00:21 CEST</pubDate><author>Matthias Klose</author><guid>https://tilde.town/~kls0e/fahrart-bike-repair-at-haus-der-materialisierung/</guid><description>&lt;p&gt;Let me recommend the
&lt;a href=https://fahrart.com
target=_blank rel="noopener noreferrer"
&gt;FahrArt bike repair service&lt;/a&gt; at
&lt;a href=https://hausderstatistik.org/hdm/
target=_blank rel="noopener noreferrer"
&gt;Haus der Materialisierung&lt;/a&gt;. Like all the manifold interesting projects located at
&lt;a href=https://osm.org/go/0MbFjan0t?way&amp;#61;48524018
target=_blank rel="noopener noreferrer"
&gt;HdM near Alexanderplatz&lt;/a&gt;, their focus is on sustainability and their service is very affordable. They work with used and new bike parts and have very friendly conditions while delivering profound and professional work.&lt;/p&gt;
&lt;p&gt;I would pop up spontaneously on a Wednesday evening while they have their open workshop and Max would install some new spare 7-shift shimano rear sprockets right away.&lt;/p&gt;
&lt;p&gt;That made me feel happy.&lt;/p&gt;
&lt;p&gt;The sprockets were saved from a bike shop clear-out as I had learned later and now experience new (heavy!) use ☺. I also had tons of questions about bikes and how to adjust all the components properly and everything was answered thoroughly and very friendly. So you can learn a lot there as well if you are interested. They also sell affordable used bikes in perfect condition and they will custom build one for you as well.&lt;/p&gt;
&lt;p&gt;FahrArt is open Monday – Friday, 12 till late. In case you are planning to go there on a Wednesday and get your bike fixed, feel free to visit our
&lt;a href=https://wiki.freifunk.net/Berlin:Treffen
target=_blank rel="noopener noreferrer"
&gt;freifunk hackspace&lt;/a&gt; and have a cold beverage with us.&lt;/p&gt;
&lt;p&gt;Update: It&amp;rsquo;s been great five years. FahrArt is gone for good.&lt;/p&gt;</description></item></channel></rss>