en datalars L.C. Bakken technical blog Wed, 02 Apr 2025 10:14:31 +0200 https://datalars.com/ Chyrp/2025.02 (Acacia) Synchronizing FreeTube settings across computers https://datalars.com/id/post/21/ Wed, 02 Apr 2025 10:14:31 +0200 https://datalars.com/2025/04/02/synchronizing-freetube-settings-across-computers/ <p><a href="proxy.php?url=https://freetubeapp.io/">FreeTube</a> is fantastic, and since it started including support for <a href="proxy.php?url=https://sponsor.ajay.app/">SponsorBlock</a>, it has become my main way of consuming YouTube content. The only downside being the fact that there is no built-in way to synchronize your setup across computers. By the power of open source, let's fix it!</p> <div style="width: 99%;"><img src="proxy.php?url=https://datalars.com/includes/thumbnail.php?file=actually-the-dankest-meme-ever-created.jpg" style="width: 500px; margin-left: auto; margin-right: auto;" /></div> <h2 id="post-21-ingredients">Ingredients</h2> <ul> <li>Two or more computers</li> <li>Software that syncs files from your filesystem (I use <a href="proxy.php?url=https://syncthing.net/">Syncthing</a>)</li> <li>Symbolic links</li> </ul> <p>If you have a sync solution already set up, syncing your settings is quite simple. FreeTube has a directory that contains all its settings, which can be added to your chosen sync software, then linked into the correct locations. In my case, I wanted sync from the Linux Flatpak version of the app that runs on my personal computer, to my MacBook, and to my work computer which runs Windows. It's also important to note that Syncthing does not follow symbolic links (to avoid infinite recursion issues), so the actual <code>FreeTube</code> settings folder needs to live in your Syncthing directory - then linked out - not the other way around. If your chosen syncing solution syncs directories in-place, then just point it at the correct locations.</p> <ul> <li>Linux Flatpak settings directory: <code>~/.var/app/io.freetubeapp.FreeTube/config/FreeTube</code></li> <li>macOS settings directory: <code>~/Library/Application Support/FreeTube</code></li> <li>Windows settings directory: <code>%appdata%\FreeTube</code></li> </ul> <p>Simply sync the <code>FreeTube</code> directory, then use a symbolic link to place it in the correct locations:</p> <ul> <li>Linux: <code>ln -s /path/to/synced/FreeTube/dir ~/.var/app/io.freetubeapp.FreeTube/config/FreeTube</code></li> <li>macOS: <code>ln -s /path/to/synced/FreeTube/dir &quot;~/Library/Application Support/FreeTube&quot;</code></li> <li>Windows: <code>mklink /D %appdata%\FreeTube C:\path\to\synced\FreeTube</code></li> </ul> <p>This will synchronize all FreeTube settings, watch history and subscriptions across all linked computers.</p> <p><em>Note that the operands are flipped between Linux/macOS <code>ln</code> and Windows <code>mklink</code>; *nix <code>ln</code> uses <code>target link_name</code> while Windows <code>mklink</code> uses <code>link_name target</code> (which is <strong>great</strong> fun when you do this relatively often and always mess up the order).</em></p> <p>After some testing with this, I also recommend adding these files to the Syncthing ignore list on each synced client (<code>.stignore</code> in the root of the Syncthing directory):</p> <pre><code>Cache_Data SingletonCookie SingletonLock SingletonSocket lockfile </code></pre> <p>These are session lock and cache files used by FreeTube that will cause sync errors if they are not ignored. Since they are ignored, though, I recommend always cleanly closing FreeTube on each computer after you're done using it - using it on more than one computer at the same time WILL cause sync conflicts.</p> <h2 id="post-21-giving-flatpak-permissions-to-access-the-new-settings-location">Giving Flatpak permissions to access the new settings location</h2> <p>As <em>Hum</em> correctly points out in the comments, you also need to give FreeTube access to the new path if its running as a Flatpak. I've accomplished this by giving it the <code>filesystem=host</code> permission via <a href="proxy.php?url=https://flathub.org/apps/com.github.tchx84.Flatseal">Flatseal</a>, a Flatpak permissions manager GUI.</p> automation freetube open-source open-source sync synchronization syncthing youtube Aa!! It's a zorting zombie! https://datalars.com/id/post/20/ Mon, 19 Aug 2024 21:47:48 +0200 https://datalars.com/2024/08/19/aa-its-a-zorting-zombie/ <p>I came across an interesting Bash issue today, as I was trying to restore a <a href="proxy.php?url=https://en.wikipedia.org/wiki/Zstd">zstd</a>-compressed <a href="proxy.php?url=https://clonezilla.org/">CloneZilla</a> Partclone image to a raw file in order to extract some data from it. For some reason, none of the solutions on the internet worked, and searching for the error message turned up no useful results. This was the command line I had constructed:</p> <pre><code>$ zstdcat nvme0n1p3.ntfs-ptcl-img.zst.* | sudo partclone.ntfs -C -r -W -s - -O image.img </code></pre> <p>Notice the glob at the end of the only argument to <code>zstdcat</code>. This only gave me:</p> <pre><code>Partclone v0.3.17 http://partclone.org Starting to restore image (-) to device (image.img) This is not partclone image. Partclone fail, please check /var/log/partclone.log ! </code></pre> <p><code>partclone</code> kept saying <code>This is not partclone image</code> no matter what I did. I did some sanity checking with the commands:</p> <pre><code>$ ls nvme0n1p3.ntfs-ptcl-img.zst.* </code></pre> <p>and also</p> <pre><code>$ ls nvme0n1p3.ntfs-ptcl-img.zst.a[a-x] </code></pre> <p>Both returned the following list:</p> <pre><code>nvme0n1p3.ntfs-ptcl-img.zst.ab nvme0n1p3.ntfs-ptcl-img.zst.ah nvme0n1p3.ntfs-ptcl-img.zst.an nvme0n1p3.ntfs-ptcl-img.zst.at nvme0n1p3.ntfs-ptcl-img.zst.ac nvme0n1p3.ntfs-ptcl-img.zst.ai nvme0n1p3.ntfs-ptcl-img.zst.ao nvme0n1p3.ntfs-ptcl-img.zst.au nvme0n1p3.ntfs-ptcl-img.zst.ad nvme0n1p3.ntfs-ptcl-img.zst.aj nvme0n1p3.ntfs-ptcl-img.zst.ap nvme0n1p3.ntfs-ptcl-img.zst.av nvme0n1p3.ntfs-ptcl-img.zst.ae nvme0n1p3.ntfs-ptcl-img.zst.ak nvme0n1p3.ntfs-ptcl-img.zst.aq nvme0n1p3.ntfs-ptcl-img.zst.aw nvme0n1p3.ntfs-ptcl-img.zst.af nvme0n1p3.ntfs-ptcl-img.zst.al nvme0n1p3.ntfs-ptcl-img.zst.ar nvme0n1p3.ntfs-ptcl-img.zst.ax nvme0n1p3.ntfs-ptcl-img.zst.ag nvme0n1p3.ntfs-ptcl-img.zst.am nvme0n1p3.ntfs-ptcl-img.zst.as nvme0n1p3.ntfs-ptcl-img.zst.aa </code></pre> <p>Wait... <em>What?</em> Notice how <code>ab</code> is the first and <code>aa</code> is the <em>last</em> substitution. Obviously, <code>aa</code> needs to be the first substitution! They need to be <code>zstdcat</code>ed <em>in order</em> for <code>partclone</code> to recognize the file, as I assume there's a file signature/magic bytes at the start of the raw file contained within the archives.</p> <p>My question was, why is my alphabetization seemingly broken? According to a web search, Bash globs will always return an ordered/alphabetized list. I was able to start the restore process by manually entering the list of files in order instead of globbing, but curiosity got the better of me and I had an inkling this was related to my system locale.</p> <p><strong>SPOILERS</strong>: It was.</p> <pre><code>$ locale LANG=en_US.UTF-8 LANGUAGE= LC_CTYPE=&quot;en_US.UTF-8&quot; LC_NUMERIC=nb_NO.UTF-8 LC_TIME=en_GB.UTF-8 LC_COLLATE=nb_NO.UTF-8 LC_MONETARY=nb_NO.UTF-8 LC_MESSAGES=&quot;en_US.UTF-8&quot; LC_PAPER=nb_NO.UTF-8 LC_NAME=nb_NO.UTF-8 LC_ADDRESS=nb_NO.UTF-8 LC_TELEPHONE=nb_NO.UTF-8 LC_MEASUREMENT=nb_NO.UTF-8 LC_IDENTIFICATION=nb_NO.UTF-8 LC_ALL= </code></pre> <p>Observe how <code>LC_COLLATE=nb_NO.UTF-8</code>. I have my system language set to English, but most other locale settings set to Norwegian. In Norwegian, <code>Aa</code>/<code>aa</code> is a common substitution for <code>Å</code>/<code>å</code>, the last character of the Norwegian alphabet. The sorting algorithm, in its infinite wisdom, seems to have decided that a file extension of <code>*.aa</code>should be sorted at the very end because of this, which breaks the argument list.</p> <p>To fix this, I set <code>LC_COLLATE</code> to <code>C</code> by issuing:</p> <pre><code>$ sudo localectl set-locale LC_COLLATE=C </code></pre> <p>This worked for about three seconds before KDE decided that my opinion is wrong, and promptly overwrote it, resurrecting <code>nb_NO.UTF-8</code> like a digital zombie.</p> <p>In KDE's System Settings &gt; Region &amp; Language section, there are a few locale related settings, but nothing about sorting. Presumably, it uses one of the other fields to <em>assume</em> the value of <code>LC_COLLATE</code>, and you know what they say about assuming.</p> <p>To <em>actually</em> fix it, I added <code>export LC_COLLATE=&quot;C&quot;</code> to my <code>~/.bashrc</code>, which seems to work, and persists between terminal sessions.</p> bash kde linux Fixing macOS' defective sleep mode with a Hammer (...Spoon) https://datalars.com/id/post/19/ Sat, 06 Jul 2024 02:18:05 +0200 https://datalars.com/2024/07/06/fixing-macos-defective-sleep-mode-with-a-hammer-spoon/ <p><small>This is kind of a followup post to <em><a href="proxy.php?url=https://datalars.com/2023/07/29/using-a-mac-mini-as-a-bedroom-pc-for-kodijellyfin-moonlight-and-youtube/">Using a Mac Mini as a bedroom PC for Kodi, Moonlight and YouTube</a></em>.</small></p> <p>It's been nearly a year since I set up a Mac Mini as my one-stop-shop for all multimedia related tasks in the bedroom. It's generally worked well, and both streaming media, games, general web browsing and PC usage has been smooth. You're rolling around in bed, playing video games, watching YouTube, or maybe the freshest episode of <a href="proxy.php?url=https://www.imdb.com/title/tt12074628/">Smiling Friends</a>. But then you fall asleep, and the fun abruptly stops.</p> <h2 id="post-19-im-gonna-wreck-it---apple-probably">&quot;I'm gonna wreck it!&quot; - Apple, probably</h2> <p>Sometimes, the computer will still be on and fully active in the morning after falling asleep using it, soaking up all those precious jiggawatts, directly out of your wallet.</p> <p><em>Why</em>, I hear you ask? <em>Maybe there is a mouse connected that causes jiggle when you shuffle around in your sleep?</em></p> <p>No, only a USB remote and a keyboard + touchpad combo device (the Logitech K400 Plus, which is absolute fucking garbage<sup>[1]</sup>) are connected.</p> <p><em>How can this be, when the system is set to sleep after 1 hour of inactivity? Surely, Thou Be Trippin'?</em></p> <p>Well, as many things Apple, it is a defect by design: Apple lets software overrule this user setting, without notifying the user, and without letting the user change this behaviour. This means that since EmulationStation - the launcher I'm using - is running in the background, and is considered a game, it inhibits system sleep. I've looked all over, and there is no way in current-day macOS to let me, the user, owner, administrator and fucking Dom Top of this machine, ignore what the system thinks, and just <em>Go The Fuck To Sleep</em> after 1 hour, no matter what. That's a huge defect, but dealing with huge defects is the bread and butter of a technical person trying to make an Apple product cooperate.</p> <h2 id="post-19-im-gonna-fix-it---me">&quot;I'm gonna fix it!&quot; - Me</h2> <p>I'm not gonna spin a yarn and complain any more about Apple today, even if I could fill pages with that kind of content. I'm a <em>Solutions</em>™ kinda guy, not an <em>Apple Sux</em>™ kinda guy.</p> <p>Here's how to fix it: Using <em><a href="proxy.php?url=https://www.hammerspoon.org/">HammerSpoon</a></em>, an application for automating macOS, along with a simple Lua script. I have never used HammerSpoon before this, but I've written a ton of Lua (for video games), so getting started was easy enough.</p> <p>Install HammerSpoon, start it, give it the appropriate permissions, set it to run at boot, and edit your <code>~/.hammerspoon/init.lua</code> to contain the following:</p> <pre><code>local function handlePowerEvent(event) if event == powerWatcher.systemDidWake or event == powerWatcher.screensDidWake then print(&quot;System woke up, restarting idle monitoring&quot;) idleTimer:start() elseif event == powerWatcher.systemWillSleep or event == powerWatcher.screensDidSleep then print(&quot;System going to sleep, suspending monitoring.&quot;) idleTimer:stop() end end local function checkSleep() local idleTime = hs.host.idleTime() if idleTime &lt; sleepThreshold then print(&quot;Idle for &quot; .. idleTime .. &quot; secs&quot;) else print(&quot;Idle time exceeded threshold, going to sleep&quot;) hs.caffeinate.systemSleep() end end sleepThreshold = 3600 --seconds powerWatcher = hs.caffeinate.watcher powerWatcher.new(handlePowerEvent):start() idleTimer = hs.timer.new(60, checkSleep) idleTimer:start() </code></pre> <p>Save the file, click the HammerSpoon icon on your menu bar, then &quot;Reload Config&quot;, and <strong><em>boom</em></strong>, you're in business.</p> <p>The script is extremely simple. It sets a timer that monitors how long the system has been idle for, which runs every 60 seconds. If the timer sees that the idle time is over the set threshold (set to 3600 seconds here - 1 hour), it tells the computer to go to sleep immediately — come hell or high water. The script additionally monitors the sleep and wake up events, in order to stop and start the timer, so it doesn't run while the computer is asleep (yeah, that can happen). It also prints some log messages to the HammerSpoon console, which you can see by clicking the HammerSpoon icon and clicking &quot;Console&quot;.</p> <p>And that's all there is to it. Apple has a long way to go when it comes to user friendliness, and definitely needs to provide an option for <em>&quot;going to sleep no matter what the system or any piece of software has to say about the present state of things&quot;</em>. I'm not going to be in charge of wording the toggle, though.</p> <hr> <p><sup>[1]</sup> The Logitech K400 Plus, also known as <em>The Frustrationator 400</em>, is e-waste that they charge money for. The touch pad comes with an infuriating acceleration that can't be turned off, which makes navigating the user interface of the computer an exercise that all but ensures that you go to bed angry. They told me I shouldn't do that, but here we are. It also looks dumb, feels cheap, and makes me nostalgic for the simpler times when I didn't own the Logitech K400 Plus.</p> automation hammerspoon lua mac macos power-handling script streaming Bash script: Reminder to swap mics with automatic pausing of all media players https://datalars.com/id/post/18/ Wed, 13 Mar 2024 23:52:05 +0100 https://datalars.com/2024/03/13/bash-script-reminder-to-swap-mics-with-automatic-pausing-of-all-media-players/ <h2 id="post-18-the-problem">The problem</h2> <p>I recently purchased a double pack of wireless microphones (specifically, <a href="proxy.php?url=https://s.click.aliexpress.com/e/_DkwQVeZ">these ones</a>) to replace my ageing and faltering wired one. I am very happy with the audio quality, their ease of use and their range, but for my specific use case, the battery life (around 3 hours) leaves a little to be desired. I mainly use these while hanging out with a friend over the internet while sharing my screen, and we'll watch movies and TV shows together. That can last a couple of hours or even most of the day, and at some point the battery for the microphone will run out.</p> <p>The first time this happened, it took me a little while to understand what was going on, as there was no beep or any indication from the microphone to signal its demise. Apparently the LED on the device will blink when it's low on battery, but that's impossible to see when it's clipped to my shirt right underneath my chin.</p> <p>But, <em><a href="proxy.php?url=https://www.youtube.com/technologyconnections">through the magic of buying two of them</a></em>, the solution is easy — swap the depleted mic with a freshly charged one, then recharge the depleted mic while you discharge the fresh one. Still, the problem of not knowing when to do that persists.</p> <h2 id="post-18-the-code">The code</h2> <p>That's right, we're writing more Bash. Normally, you could just set a timer for your phone or your computer, but we're watching content that has sound whilst wearing headphones. What I wanted was a solution that could pause all playing media and tell me to swap my mic out, so that's exactly what I wrote:</p> <p><code>mic-change.sh</code>:</p> <pre><code>#!/usr/bin/env bash matches=$(playerctl -l) IFS=$'\n' read -r -d '' -a matchedPlayers &lt;&lt;&lt; &quot;$matches&quot; numPlayers=${#matchedPlayers[@]} ((numPlayers--)) # To use zero based indexing for i in $(seq 0 &quot;$numPlayers&quot;); do currentPlayer=${matchedPlayers[&quot;$i&quot;]} status=$(playerctl -p &quot;$currentPlayer&quot; status) if [[ &quot;$status&quot; == &quot;Playing&quot; ]]; then playerctl -p &quot;$currentPlayer&quot; play-pause if [[ &quot;$currentPlayer&quot; == &quot;kodi&quot; ]]; then sleep 1 fi fi done mplayer &quot;/opt/sfx/mic-change.mp3&quot; </code></pre> <h3 id="post-18-how-to-use">How to use</h3> <p>Run the script when you turn on your microphone, and re-run it whenever you swap your mic. Examples:</p> <ul> <li>Run a sleep then the script from the terminal: <br /><code>sleep 9000; /opt/scripts/mic-change.sh</code></li> <li>Put the sleep at the start of the script (after the shebang) and run it</li> <li>Use a timer app that can launch scripts when the timer finishes, like <a href="proxy.php?url=https://flathub.org/apps/org.kde.kclock">KClock</a></li> </ul> <p><small><em>(9000 seconds is 2h 30m)</em></small></p> <h2 id="post-18-script-explanation">Script explanation</h2> <p>This script uses <code>playerctl</code> to pause all currently playing media players via the <a href="proxy.php?url=https://specifications.freedesktop.org/mpris-spec/latest/">MPRIS D-Bus specification</a>. Most players for Linux support this natively. <a href="proxy.php?url=https://www.kodi.tv/">Kodi</a>, my media player of choice, does not, but support can easily be added through <a href="proxy.php?url=https://github.com/wastis/MediaPlayerRemoteInterface">an addon</a>. The script goes through every currently registered media player, and checks if it is currently playing. If it is, it pauses it. If it is Kodi, it waits a second before doing anything else, since Kodi has a ˜1 sec delay when pausing before audio stops playing (and also an annoying corresponding ˜1 sec delay before audio starts playing again once you unpause). Finally, it plays the sound defined in the last line of the script using <code>mplayer</code>, which in my case is a TTS voice named Onyx telling me to <a href="proxy.php?url=https://ttsmp3.com/ai">&quot;Change your mic, motherfucker.&quot;</a></p> <p>If you'd like to get the same microphones, which are very good despite the relatively short battery life, you can get them <a href="proxy.php?url=https://s.click.aliexpress.com/e/_DkwQVeZ">on AliExpress here</a>.</p> <p><small><em>The product links in this article are affiliate links. If you buy something using them, I may earn a small commission at no extra cost to you.</em></small></p> audio automation bash microphone script sound "Retrocorder" - Using a spare PC to make a fully automated retro-gaming recording solution https://datalars.com/id/post/16/ Tue, 26 Sep 2023 19:43:00 +0200 https://datalars.com/2023/09/26/retrocorder-make-an-automated-retro-gaming-recording-solution/ <h2 id="post-16-persistent-nostalgia">Persistent nostalgia</h2> <p>The past few months, I've spent some time to set up a room in my house to be a pretty sweet space dedicated to retro-gaming. This includes a vintage PC, a CRT TV, and a multitude of classic games consoles hooked up to four RCA/AV switches. This works well, and to play the console we want, one can just flip a couple of switches instead of having to unplug and shuffle around literally 8 different sets of RCA cables.</p> <p>Thing is, when you're playing games, especially during couch coop, <em>things tend to happen</em>. Some of these things, depending on the types of games you play, can be utterly hilarious, and worth saving for posterity. With the two last generations of consoles I own, PS5, PS4 and the Nintendo Switch, you can just press a single button on the controller to save recent gameplay as a file to the system storage, which is awesome. When you also enjoy playing on 20-30 year old game consoles as well, this part becomes a bit trickier.</p> <h2 id="post-16-emergent-technologies">Emergent technologies</h2> <p>Enter: The <strong>&quot;Retrocorder&quot;</strong> - the dedicated computer I've set up to continuously record all retro gaming gameplay as it happens.</p> <p>For reference, this is what my setup looked like <em>before</em> Retrocorder:</p> <p><img src="proxy.php?url=https://datalars.com/includes/thumbnail.php?file=retrogaming-chart-before-2.png" alt="" loading="lazy"></p> <p><small><em>(If I stick with 3-port switches, I can only get one more console before I have to add another level to the hierarchy</em> 😳)</small></p> <p>Since all the signals involved here are analog, you'd think there would be some degradation at each step. That might be true, but none are perceptible to me in regards to image or sound quality, and the cables between the switches are short, minimizing signal loss.</p> <h2 id="post-16-hardware">Hardware</h2> <p>If you want to follow along at home, here's a parts list:</p> <ul> <li> <p>Spare PC with a processor powerful enough to record and encode 480p/576p video in real time (using h264 this roughly means any CPU from the last 10 or so years)</p> </li> <li> <p>One of the following:</p> <ul> <li><a href="proxy.php?url=https://s.click.aliexpress.com/e/_DnNJDBJ">HDMI capture card</a> (recommended)</li> <li><a href="proxy.php?url=https://s.click.aliexpress.com/e/_DeUGWYH">RCA/AV capture card</a></li> </ul> </li> <li> <p><a href="proxy.php?url=https://s.click.aliexpress.com/e/_DE6yl6z">AV2HDMI converter</a> (if using HDMI capture card)</p> </li> <li> <p>3x <a href="proxy.php?url=https://s.click.aliexpress.com/e/_DmnHlFr">RCA Y-splitters</a> (RCA female to 2RCA male)</p> </li> <li> <p>3-6x <a href="proxy.php?url=https://s.click.aliexpress.com/e/_Dn4vfTn">RCA cables</a> (female to male)</p> </li> </ul> <p><small><em>The above links are affiliate links. If you buy something using them, I may earn a small commission at no extra cost to you.</em></small></p> <p>Using these parts, we can split the RCA cables right before the TV, and lead one end to the TV, and the other into our capture card, like so:</p> <p><img src="proxy.php?url=https://datalars.com/includes/thumbnail.php?file=retrogaming-chart-after-2.png" alt="" loading="lazy"></p> <p>As you can see from the chart, the splitters are inserted between the first switch and the TV. This way, no latency is introduced to the TV, so we can play our games normally without any changes to our gameplay. We can also swap game consoles mid-session and have no interruptions to the video recording, since the video is captured from the first switch.</p> <p>Regarding the 3-6x RCA cables listed: 3 of them go from the first split of the RCA Y splitter to the TV. As for the other 3 - depending on how your splitters and AV2HDMI box look, you may or may not need them. I was able to plug the second split of the RCA Y splitter directly into the AV2HDMI box without using the extra 3 cables - your mileage may vary.</p> <p>The reason I recommend the HDMI capture card over the RCA/AV capture card, is because AV capture cards tend to have issues switching between NTSC and PAL video signals. I mix NTSC and PAL video signals all the time, as I own consoles and games of both regions. My CRT TV supports both signals, which isn't a given for any CRT TV either, so make sure yours does before you mix signals and end up with unusable video files. The HDMI route isn't immune to this issue either, but the AV2HDMI converter box seems to handle this a lot better, and seems to send the same signal to the capture card regardless of input. I also experienced horrible buzzing noises during recording when using my AV capture card, presumably due to interference, but that might be down to a faulty card that's been stored in a box for 10 years or more.</p> <p>The AV2HDMI converter box works great, and will detect a change in signal automatically. In my testing and usage, I have found only one combination of consoles/game regions that will throw it for a loop - playing NTSC games on a PAL GameCube (via Swiss). This outputs a black and white, wavy signal from the AV2HDMI box, presumably because the GameCube is outputting an esoteric (or out-of-sync) signal that it doesn't quite understand. The TV shows the signal just fine, however. Playing PAL games on the same console works fine, so I've solved this issue by just using PAL games instead. The PS2, where I also mix NTSC and PAL games on a PAL console, does not exhibit this problem, and both regions display fine when captured.</p> <h2 id="post-16-software">Software</h2> <p>Now that all the hardware is plugged together, it's time to make this as seamless as possible using software.</p> <p>For the operating system, I use my default go-to for projects such as these: The latest Kubuntu LTS. KDE is my favourite desktop environment, I'm very familiar with it at this point, and the Ubuntu LTS base provides a solid foundation.</p> <p>The hardware of the PC is modest, as this is an ancient PC that I used as a daily driver 10 or so years ago, only upgraded with an SSD:</p> <ul> <li>CPU: AMD FX-6100 Six-core @ 3.300GHz</li> <li>GPU: AMD ATI Radeon HD 7870 GHz Edition</li> <li>RAM: 8 GB</li> <li>SSD: 128 GB</li> </ul> <p>After installation, we can start installing some software, and setting some desired options.</p> <p>I've installed the following:</p> <ul> <li><a href="proxy.php?url=https://www.nomachine.com/">NoMachine</a> - for remote access from my main PC</li> <li><a href="proxy.php?url=https://obsproject.com/">OBS Studio</a></li> <li><a href="proxy.php?url=https://github.com/pschmitt/obs-cli">obs-cli</a> - Make sure you get you get the one from <code>pschmitt</code> - the other ones I've found are all defective or outdated</li> </ul> <p>Now, set up your scene in OBS. At the bottom, add a new video source.</p> <p><img src="proxy.php?url=https://datalars.com/includes/thumbnail.php?file=2023-09-2617-57.png" alt="" loading="lazy"></p> <p>I had to pick YU12 to get the correct image for my setup, but yours may be different. The AV2HDMI converter box outputs a 720p or 1080p signal, configurable by a physical switch on the unit itself. I set mine to 720p, and the recording resolution to 576p, as no console in my setup outputs anything higher than that over AV anyway.</p> <p>Now that that's out of the way, we just need to configure a few things about the recording environment. In OBS, go to <strong>File &gt; Settings</strong>, then <strong>Output</strong>, and set <strong>Output Mode</strong> to <strong>Advanced</strong>. Click the <strong>Recording</strong> tab, then set your desired settings:</p> <p><img src="proxy.php?url=https://datalars.com/includes/thumbnail.php?file=2023-09-2618-11.png" alt="" loading="lazy"></p> <p>My changes from the default:</p> <ul> <li>I've used <code>/opt/retrocord</code> as the folder to store all my recordings.</li> <li>I've checked &quot;Generate File Name without Space&quot;</li> <li>I've set the video encoder to x264 as it is light on CPU and produces files of a manageable size</li> <li>I've enabled &quot;Automatic File Splitting&quot;</li> <li>I've set &quot;Split Time&quot; to 5 min</li> </ul> <p>The reason I've enabled automatic file splitting is twofold: To not end up with files of gargantuan sizes, and to let me delete older files in the directory when it becomes too large, without having to split file and review what I want to save.</p> <p>Click OK. Next, choose <strong>Tools</strong> in the menu bar, then click <strong>WebSocket Server Settings</strong>. Check <strong>Enable WebSocket server</strong>, then click <strong>Show Connect Info</strong>. Note down the <strong>Server Port</strong> and the <strong>Server Password</strong>, as you'll be needing it soon. Close the connection info window and click OK in the WebSocket Server window.</p> <h2 id="post-16-scripting-scripts">Scripting scripts</h2> <p>Your recording environment should now be ready to use. The next step is to automate this, so you don't need to manually interact with OBS to make it do its thing. I accomplish this using two scripts; a login script and a logout script.</p> <p><code>/opt/scripts/retrocorder-start.sh</code>:</p> <pre><code>#!/usr/bin/env bash obs --startrecording &amp; </code></pre> <p><code>/opt/scripts/retrocorder-stop.sh</code>:</p> <pre><code>#!/usr/bin/env bash obs-cli --host localhost --port 4455 --password abcde12345 record stop sleep 1 kill -s TERM &quot;$(pidof obs)&quot; sync </code></pre> <p>In <code>retrocorder-stop.sh</code>, you need to change two values in the second line: the port and the password that you noted down earlier (<strong>4455</strong> and <strong>abcde12345</strong> in the example above). The reason we need <code>obs-cli</code> in the first place and the OBS WebSocket Server to be running, is that while OBS can let you send it an argument to start recording, it has no such argument to stop the recording in progress (for some god-awful reason).</p> <p>As you might have noticed, this setup might end up with a full disk after enough usage, so we're gonna have to deal with that with another script. This time, we're gonna set up a cron job to be run once an hour, to prune the oldest videos in the recording directory, once a size threshold has been exceeded:</p> <p><code>/opt/scripts/retrocorder-prune.sh</code>:</p> <pre><code>#!/usr/bin/env bash cd /opt/retrocord || exit limitBytes=$((50*1024*1024*1024)) # 50 GiB currentDirSize=&quot;$(du -bs | awk '{print $1}')&quot; if [[ &quot;$currentDirSize&quot; -gt &quot;$limitBytes&quot; ]]; then while [[ &quot;$currentDirSize&quot; -gt &quot;$limitBytes&quot; ]]; do purgeFile=$(ls -rt *.mkv | head -n 1) rm &quot;$purgeFile&quot; currentDirSize=&quot;$(du -bs | awk '{print $1}')&quot; done fi </code></pre> <p>This script deletes the oldest files in a directory until the total directory size is less than 50 GiB. Change the second line of the script to point to your recording directory, and the third line to reflect how much disk space you'd like to allocate to recordings. I've set mine to 50 GiB, as that is plenty, and leaves lots of headroom on the 128 GB SSD.</p> <p>On my setup, 5 mins of recording equals around 100 MiB. This means that I can record &gt; 42 hours of gameplay before the script starts purging - more than enough time to save any clips I want to keep!</p> <details> <summary>Quick maffs</summary> 50 GiB * 1024 MiB per GiB = 512,000 MiB allocated<br /> 512,000 MiB / 100 MiB per 5 mins = 512 files á 100 MiB<br /> 512 files * 5 minutes per file = 2560 minutes<br /> 2560 minutes ≈ 42.66 hours ≈ 1.77 days </details> &nbsp; <p>Finally, put the script in your crontab. First, edit your crontab:</p> <pre><code>$ crontab -e </code></pre> <p>Then append a new line at the end of the file:</p> <pre><code>0 * * * * /opt/scripts/retrocorder-prune.sh </code></pre> <p>Save and close the file (if the editor is <code>nano</code>, press, Ctrl+X, then Y, then Enter).</p> <p>This will make the script run once an hour.</p> <h2 id="post-16-setting-settings">Setting settings</h2> <p><strong>Note</strong>: These settings are for KDE Plasma. There are most likely equivalents for these settings in all other major DEs.</p> <p>Once the above scripts are created, in Plasma, go to <strong>System Settings &gt; Startup and Shutdown &gt; Autostart</strong>. Click <strong>Add</strong> at the bottom of the window, then pick <strong>Add Login Script</strong>. Navigate to and pick <code>/opt/script/retrocorder-start.sh</code>. Now do the same for the logout script: Click <strong>Add &gt; Add Logout Script</strong>, then navigate to and pick <code>/opt/scripts/retrocorder-stop.sh</code>. This will automatically start and stop recording when you log in and out of the computer.</p> <p>To make this completely automatic, you'll also need to make sure you're automatically logged in to the computer. Also in <strong>Startup and Shutdown</strong>, pick <strong>Login Screen (SDDM)</strong>, then click the button labeled <strong>Behaviour</strong> on the bottom left. Next, check the box next to <strong>Automatic log in</strong>, then choose your user and session on the same line. Click <strong>Apply</strong>.</p> <p>Also in <strong>Startup and Shutdown</strong>, pick <strong>Desktop Session</strong>, then uncheck the box next to <strong>Logout Screen - Show</strong>. This makes sure when you request a shutdown, it is done immediately.</p> <p>The next destination is still in <strong>System Settings</strong>, under <strong>Power Management</strong> this time (called <strong>Energy Saving</strong> in earlier versions of Plasma). Uncheck all checkboxes, then check <strong>Button events handling</strong>. In the drop-down box <strong>When power button pressed</strong>, pick <strong>Shut down</strong>.</p> <p>Lastly, if you wish access this computer over SSH, install and enable <code>openssh-server</code>:</p> <pre><code>$ sudo apt update $ sudo apt install openssh-server $ sudo ufw allow ssh </code></pre> <p>This allows you to log in remotely via SSH. Additionally, it lets you use <a href="proxy.php?url=https://en.wikipedia.org/wiki/Files_transferred_over_shell_protocol">FISH</a> to easily copy files from the Retrocorder to your main machine; SSH-enabled servers can be accessed in Dolphin by using the <code>fish:</code> URI scheme in the address bar:</p> <pre><code>fish://192.168.0.123/ </code></pre> <p>You could also set up an NFS or SMB share, but that's out of scope for this post.</p> <h2 id="post-16-headless-chicken">Headless chicken</h2> <p><strong>Note</strong>: This section mostly applies to desktop PCs. If you're using a laptop, you're more or less done.</p> <p>At this point you should be in a state where everything is automatic. Starting with the PC off, when you press the power button, the computer will boot, log you in, start OBS and start recording. Once an hour, your recording directory will be checked, and if it's too big, the oldest files will be deleted. OBS will keep recording until you hit the power button. Once you hit the power button, OBS will stop recording, close, the disks will sync, and the computer will turn off.</p> <p>Wouldn't it be cool if you didn't need that pesky monitor, keyboard and mouse?</p> <p>In most cases, if you don't have a display attached, the computer will not boot to a graphical environment. There's two ways to fix this, either by creating a dummy display (Xorg only), or by getting a physical dummy connector which will fool your computer into thinking a display is attached. There are dummy connectors available for all sorts of display connectors, but this post will focus on the software solution, as it works great for me.</p> <p>This following solution only works on Xorg. I don't know if Wayland has an equivalent method of making a dummy display, but I'm sure you could find something by searching the web.</p> <p>Create the following file:</p> <p><code>/usr/share/X11/xorg.conf.d/xorg.conf</code>:</p> <pre><code>Section &quot;Device&quot; Identifier &quot;Configured Video Device&quot; Driver &quot;dummy&quot; EndSection Section &quot;Monitor&quot; Identifier &quot;Configured Monitor&quot; HorizSync 31.5-48.5 VertRefresh 50-70 EndSection Section &quot;Screen&quot; Identifier &quot;Default Screen&quot; Monitor &quot;Configured Monitor&quot; Device &quot;Configured Video Device&quot; DefaultDepth 24 SubSection &quot;Display&quot; Depth 24 Modes &quot;1280x720&quot; EndSubSection EndSection </code></pre> <p>If you're not on *ubuntu, <code>xorg.conf</code> might live somewhere else, such as <code>/etx/X11/xorg.conf</code>. In many cases, it doesn't exist and must be created - searching the web is your friend again here.</p> <p>Save this file, which configures a dummy 1280x720 display. In my experience, increasing the resolution doesn't do anything, the dummy display seems to max out at that resolution.</p> <p>Restart your computer, and recording should now start, even without a display attached! It will also enable you to remote control the desktop using <a href="proxy.php?url=https://www.nomachine.com/">NoMachine</a> or equivalent remote control software.</p> <p>You should now have a fully automated recording solution for your retro gaming setup! :) The only thing you need to do now is press the power button to turn the computer on and start recording, and press it again to shut down when you're done.</p> <p>Here's a sample of gameplay recorded from my Retrocorder. It's by no means perfect, but it's more than good enough for my purposes - saving fun or memorable bits of gameplay. This clip is me playing the 2006 PS2 game <a href="proxy.php?url=https://en.wikipedia.org/wiki/Black_(video_game)"><em>Black</em></a>:</p> <iframe class="content_embed youtube_embed" src="proxy.php?url=https://www.youtube.com/embed/gwVPz22yih0" title="Embedded content from YouTube" allowfullscreen loading="lazy"></iframe> <p>YouTube truncates this to 480p, so if you want the source file, you can get it <a href="proxy.php?url=https://mega.nz/file/G9IlBY7J#KHluoaliC1IXvX7HV-7do3eN06LGX6kALmAuUeZTY20">here</a>!</p> <h3 id="post-16-closing-thoughts">Closing thoughts</h3> <p>The first time I tried booting my PC after setting up the dummy display, it would not start up, much to my annoyance. Turns out this motherboard is one of those who will halt the boot process if a keyboard isn't connected - easily fixed by changing the BIOS settings not to halt on keyboard errors.</p> <p>This was a fun project to set up!</p> <style>summary { cursor: pointer; }</style> automation bash gaming kde linux playstation playstation-2 programming retro-gaming script How to make a partially defective speaker stay powered on (or: The Five Stages of Annoyance) https://datalars.com/id/post/15/ Thu, 07 Sep 2023 20:18:21 +0200 https://datalars.com/2023/09/07/how-to-make-a-partially-defective-speaker-stay-powered-on-or-the-five-stages-of-annoyance/ <h2 id="post-15-acquisition">Acquisition</h2> <p>I bought a new TV a year and a half ago. Much to my dismay, I discovered that while the picture quality is great, the audio quality is not, unlike my previous TV. My daily driver Linux PC is connected to my TV, and is my main entertainment hub for TV shows, movies and video games, and also general PC usage, so I figured I had to get an external speaker or sound bar. My dad had a spare speaker which he had replaced with a newer unit, so I inherited the spare - a &quot;<em>Geneva Sound System Model S DAB</em>&quot;.</p> <p>It came with a small caveat: the speaker will not turn <em>on</em> using the remote control. Mind you, it will turn <em>off</em> just fine with the same exact button, and every other button on the remote works as advertised - it just will not turn on using the remote. I figured that this was no big deal, and accepted the speaker with thanks. I get the speaker situated and hooked up, and the audio quality is great - a big upgrade from the built-in TV audio. Great for music, great for movies, great for games, just all-round great...</p> <h2 id="post-15-perturbation">Perturbation</h2> <p>...until I read some news, or do anything that doesn't produce any sound for a while. This is when the European Commission decides to take a retroactive shit in my cereal. Since 2009, audiovisual equipment such as speakers are <a href="proxy.php?url=https://commission.europa.eu/energy-climate-change-environment/standards-tools-and-labels/products-labelling-rules-and-requirements/energy-label-and-ecodesign/energy-efficient-products/mode-standby-and-networked-standby-devices_en"><strong>required</strong> to switch to a low-power mode after a reasonable amount of time</a>. In and of itself, that is a good requirement, and will save power when such devices are not in use. Sometimes it's a bad requirement, and that is when:</p> <p>     a) the automatic standby feature can't be disabled, and<br />      b) the unit can't be powered on with the remote control</p> <h2 id="post-15-bargaining">Bargaining</h2> <p>This particular set of circumstances means that whenever I'm using the computer, and switch between tasks that produce sound, and those that don't, I risk the speaker powering down, and having to get up, walk over to the speaker underneath the TV, and power it back up manually. That wouldn't be so bad if it was a once-a-day thing, but the auto-standby timeout for the speaker is fixed at 30 minutes, so you might see how this gets real old, real fast. The speaker itself works fine, and it's a shame to generate e-waste from something that's only partially defective, so I decide to stick with it. I wouldn't blame someone for chucking it and replacing it with a new unit though, but I am particularly stubborn.</p> <p>Upon discovering this, the first thing I do is start searching the web for <a href="proxy.php?url=http://ch.genevalab.com/wp-content/uploads/2016/08/GenevaSound-S-Manual_Multilingual.pdf">the manual for the device</a>, to see if I can address point a) above: turning off the auto-standby function, but I come up short. There doesn't seem to be a way to disable this functionality at all. Since I wouldn't know the first thing about fixing the hardware side of this (fixing the remote not being able to turn the unit on any more), I default to trying to work around it through software.</p> <p>My first attempt to trick the speaker into staying awake is playing a sound on an interval. A sound which is in frequency range of the speaker, but out of range of my ears. According to specs <a href="proxy.php?url=https://radiospesialisten.no/produkt/geneva-model-s-dab-bt-matt-sort/">found online</a>, the frequency range for the unit is 75 Hz to 20 kHz, so I try both ends of the spectrum. Using Audacity, I generate 5 second tones ranging from 76 Hz to 19 kHz, and make a script that plays the tone every 10 minutes, and slowly over the course of a few days, work my way through all the generated tones to see if any will keep the speaker awake.</p> <p>It doesn't work. None of the tones do. They all play (confirmed by dropping in an audible .wav in place of the generated tones), but it seems as though the speaker is ignoring them. The 17 kHz tone had the added bonus of viscerally startling my friend who could hear the sound (I couldn't), asking what the hell that was.</p> <p>Most likely the internal logic that checks for sound, checks for loudness/dB values, or some other obscure black magic fuckery I don't understand (spoilers: I am not an audio technician, and possess only cursory knowledge of audio specifics).</p> <p>My second attempt is a much less sophisticated one: play a short, normal, audible sound every 20 minutes or so. I picked out a ~1 second audio file from my OS, and made it play every 20 minutes. This also didn't work. My conclusion is now that the aforementioned internal logic not only checks for loudness/amplitude, but also has a polling rate greater than a second (or at least, greater than the length of the audio file in question). I don't want to play a longer file either since it's on a fixed interval, it will play over any other media I am consuming, and become another point of annoyance.</p> <h2 id="post-15-acceptance">Acceptance</h2> <p>At this point, I more or less resign to my fate, and continue using the speaker without any software magic to try to keep it alive. I do the whole song and dance of getting up and turning the speaker back on when I need it, several times a day. My record is 7 times in one long day of intensive programming interspersed by YouTube video breaks.</p> <h2 id="post-15-redemption">Redemption</h2> <p>A year and a half passes, to the present day. I am working on some other project, when I notice the speaker turning off again, and spontaneously get an idea. What if I continuously monitor my audio output, and only play an audible sound when it's been quiet for a while? I haven't done anything like that in the past, but after a ton of web searching, and trial and error, I come up with a solution. This relies on using PulseAudio (or PipeWire with its backward compatibility with PA).</p> <p>That's right, there's finally going to be some code in this article!</p> <p><code>anti-energy-saving.sh</code>:</p> <pre><code>#!/usr/bin/env bash sinkToMonitor=&quot;alsa_output.pci-0000_06_00.1.hdmi-stereo&quot; # find with `pactl list short sinks` soundToPlay=&quot;/usr/share/sounds/freedesktop/stereo/message-new-instant.oga&quot; tmpOutputSample=&quot;/tmp/output-sample.flac&quot; # saves the sample in ram and overwrites it every loop timeoutSecs=1500 # timeout in seconds - 1500s = 25m trap quit SIGINT quit() { printf &quot;\nSIGINT received, cleaning up and exiting\n&quot; rm &quot;$tmpOutputSample&quot; exit } secsSinceLastSound=0 while true; do currDefaultSink=$(pacmd list-sinks | grep &quot;\* index&quot; -A1 | awk '/name:/ {print $2}') if [[ &quot;$currDefaultSink&quot; = &quot;&lt;$sinkToMonitor&gt;&quot; ]]; then timeout 5 parecord --channels=1 --file-format=flac --device &quot;$sinkToMonitor.monitor&quot; &quot;$tmpOutputSample&quot; meanNorm=$(sox &quot;$tmpOutputSample&quot; -n stat 2&gt;&amp;1 | awk '/Mean +norm:/ {print $3}') if (( $(echo &quot;$meanNorm &gt; 0.000700&quot; | bc -l) )); then secsSinceLastSound=0 echo &quot;Sound threshold reached, timer reset...&quot; else secsSinceLastSound=$((secsSinceLastSound+5)) echo &quot;Sound threshold not reached, timer is at $secsSinceLastSound/$timeoutSecs seconds...&quot; fi if [[ &quot;$secsSinceLastSound&quot; -ge $timeoutSecs ]]; then echo &quot;Timeout reached, playing sound and resetting timer...&quot; paplay &quot;$soundToPlay&quot; paplay &quot;$soundToPlay&quot; secsSinceLastSound=0 fi else echo &quot;Active sink changed, sleeping...&quot; secsSinceLastSound=0 sleep 10 fi done </code></pre> <p>To use this on your system, you'll need to change the values of the variables <code>sinkToMonitor</code>, <code>soundToPlay</code> and <code>timeoutSecs</code> to fit your configuration.</p> <ul> <li><code>sinkToMonitor</code> needs to hold the name of your default audio sink, which you can find with <code>pactl list short sinks</code></li> <li><code>soundToPlay</code> is a path to the sound you wish to play when the inactivity timer has been reached</li> <li><code>timeoutSecs</code> is the number of seconds of inactivity to wait before playing the keep-alive-sound</li> </ul> <p>Make the script executable with <code>chmod +x anti-energy-saving.sh</code>.</p> <p>You'll also notice that I call <code>paplay</code> twice to play the same sound twice. If you use a longer audio file, you won't have to do this, I just preferred this short and inoffensive sound to play whenever the speaker is nearing its auto-standby timeout.</p> <p>If you don't speak Bash, this is a quick run-down of how the script functions:</p> <ul> <li>Checks whether you're currently using your default audio sink, if not, sleeps for 10 seconds. I added this because I sometimes use a Bluetooth headset, and don't want the script running on that - only the HDMI output.</li> <li>If you're using your default audio sink, the script will record 5 seconds' worth of audio that comes out of your speaker to RAM (<code>/tmp</code>)</li> <li>The audio file is run through <code>sox -n stat</code> to get the average volume of the samples in the clip - if it exceeds a set threshold, the script will reset the inactivity timer. The reason this comparison is &gt; 0.0007 instead of just any value &gt; 0 is because of the aforementioned speaker polling rate; we need to make sure enough sound has been played lately that the speaker won't ignore it.</li> <li>If the audio clip didn't exceed the threshold, 5 seconds is added to the inactivity timer.</li> <li>If the inactivity timer is greater than or equal to the timeout value, a sound is played twice to keep the speaker alive, and the inactivity timer is reset</li> <li>Repeats ad infinitum</li> </ul> <p>You can remove all <code>echo</code>es if you'd like, they are just a remnant of me debugging this in an interactive terminal. The script is designed to run non-interactively in the background without any user input.</p> <h3 id="post-15-conclusion">Conclusion</h3> <p>The script works great for me and my situation, and I haven't had a single incident of speaker snooze since implementation. I've set it to run on start-up, and it uses very little CPU and RAM. It also won't interrupt any other audio I am playing, since there's a little more thought to it beyond a simple <code>sleep 900</code>.</p> audio automation bash linux pulseaudio script sound Quickie: Using hdldump to transfer PS2 HDD games under Linux https://datalars.com/id/post/14/ Thu, 10 Aug 2023 01:49:00 +0200 https://datalars.com/2023/08/10/quickie-using-hdldump-to-transfer-ps2-hdd-games-under-linux/ <p>The PS2 homebrew scene is an absolute mess, and whenever I try to find any information on any operation about it online, I find the following:</p> <ul> <li>A truckload of conflicting information</li> <li>A myriad of different guides spanning back 20 years</li> <li>A bushel of different software tools, none of which are usually available on Linux</li> <li>And a partridge in a pear tree</li> </ul> <p>This time, all I needed to do was to figure out how to get my ISO and BIN/CUE PS2 backups onto an internal HDD for playing through Open PS2 Loader (OPL). All of the above points of note came into play, but after digging and sorting through it all for a bit, I found a reasonable way to do this without having to involve a Windows computer:</p> <ol> <li>HDL Dump Helper GUI includes a Linux x86 build of hdldump. <a href="proxy.php?url=https://www.psx-place.com/resources/hdl-dump-helper-gui-for-linux-windows-by-simon.707/history">Grab it from PSX-Place</a>.</li> <li>Extract the rar, move <code>hdld_2_3/files/hdl_dump_090</code> to <code>/usr/bin/hdldump</code></li> <li><code>chmod +x /usr/bin/hdldump</code></li> <li>You now have hdldump for Linux CLI, hooray!</li> </ol> <p>Every guide I looked at said that one of the downsides of hdldump is that it doesn't do batch operations. Who needs built-in batch operations when you have Bash?</p> <p><code>/opt/scripts/batch_hdl.sh</code>:</p> <pre><code>#!/bin/bash shopt -s nullglob nocasematch for i in *.iso do gameName=&quot;${i%.*}&quot; echo &quot;Injecting ${gameName}...&quot; hdldump inject_dvd &quot;$1&quot; &quot;${gameName}&quot; &quot;${i}&quot; echo &quot;Finished injecting ${gameName}.&quot; done for i in *.cue do gameName=&quot;${i%.*}&quot; echo &quot;Injecting ${gameName}...&quot; hdldump inject_cd &quot;$1&quot; &quot;${gameName}&quot; &quot;${i}&quot; echo &quot;Finished injecting ${gameName}.&quot; done </code></pre> <p>Presto. Make the script executable (<code>chmod +x batch_hdl.sh</code>), <code>cd</code> to the directory with your games, then run the script with your PS2 HDD as the only argument.</p> <p>For added pizzazz, put <code>alias hdlbatch=&quot;/opt/scripts/batch_hdl.sh&quot;</code> in your <code>~/.bashrc</code> or <code>~/.bash_aliases</code>, then <code>source ~/.bashrc</code>/<code>source ~/.bash_aliases</code>. Now you can run the script from any directory using <code>hdlbatch /dev/sdg</code> to pump that HDD chock full of more games you'll never play.</p> <pre><code>$ hdlbatch /dev/sdg Injecting Beyond Good &amp; Evil... Finished injecting Beyond Good &amp; Evil. Injecting Burnout 3 - Takedown... Finished injecting Burnout 3 - Takedown [...] </code></pre> automation bash gaming linux playstation playstation-2 programming script video-games Using a Mac Mini as a bedroom PC for Kodi, Moonlight and YouTube https://datalars.com/id/post/13/ Sat, 29 Jul 2023 13:45:27 +0200 https://datalars.com/2023/07/29/using-a-mac-mini-as-a-bedroom-pc-for-kodijellyfin-moonlight-and-youtube/ <h1 id="post-13-issues-in-the-bedroom">Issues in the bedroom</h1> <p>I have a wall-mounted TV in my bedroom. This TV has a PS4 and a PC hooked up to it. I use the PS4 to remote play my PS5 (which is situated in the living room), and in the past, have used the PC for Kodi/Jellyfin and YouTube in bed, using a USB remote control with support for moving the mouse cursor. Unfortunately, it doesn't do that very well, as the Wi-Fi adapter I have for it keeps presenting issues under Linux. No matter how much I re-compile and reenable the drivers, it just won't work properly, and will randomly cut out after a while. This also happens with several different adapters, so I've kinda just left it there, and used the PS4 to access the web UI of Jellyfin. This is not ideal, and I don't want to spend any more hours fighting with it.</p> <h1 id="post-13-free-fruit">Free fruit</h1> <p>I don't usually use Apple products, but I had a Mac Mini lying around as surplus after having replaced all Macs at my place of work with Windows computers. It's a shame to let it just lie around, collecting dust, so why not use it for something useful, and also solve my bedroom problems (heh) in one fell swoop?</p> <p>My criteria were as follows:</p> <h3 id="post-13-absolutely-crucial">Absolutely crucial</h3> <ul> <li>Support for my USB remote (it identifies itself as a keyboard and mouse so almost anything would fill this criteria)</li> <li>Support for a wireless game controller (DS4)</li> <li>Be able to run Kodi and Jellyfin for Kodi</li> <li>Be able to run Moonlight, for remote playing PC games</li> <li>Be able to watch YouTube with uBlock Origin and SponsorBlock</li> <li>Be able to control the whole machine using just my remote and my gamepad</li> </ul> <h3 id="post-13-nice-to-have">Nice to have</h3> <ul> <li>Be able to emulate some games natively, and use the gamepad to do so</li> </ul> <h1 id="post-13-collection-and-assimilation">Collection and assimilation</h1> <p>After stumbling upon Retro Game Corps' <a href="proxy.php?url=https://www.youtube.com/watch?v=XohIHgpe1NI">video on Retro Gaming on a Mac Mini</a>, I figured that the Mac Mini I had lying around would be perfect for this, as long as the usual <em>&quot;Whoops! Can't do that on a Mac!&quot;</em> problems didn't stand in the way. I would use <a href="proxy.php?url=https://es-de.org/">EmulationStation Desktop Edition</a> (ES-DE) as the shell to launch Kodi, Moonlight and YouTube from.</p> <p>I already had the Mac and the USB remote, but not an extra controller. As luck would have it, my step-brother's girlfriend had one she didn't need, so I inherited her bright blue DualShock 4, which is perfect. The other DS4 in the bedroom is black, and will continue to be hooked up to the PS4, while the blue one will be used with the Mac. Unlike in my experience on Windows, pairing and using the DS4 Just Werks™, and was ready to use right away after pairing.</p> <h1 id="post-13-software-setup">Software setup</h1> <p>After installing the apps I wanted (Dolphin, RPCS3, RetroArch, Kodi, Moonlight), and ES-DE, I was ready to get going. I also installed NoMachine for remote access, in case I want to do maintenance on this machine without having to lie in bed to do so. It took a bit of web searching to find documentation for how to launch arbitrary apps from EmulationStation, but it wasn't really difficult.</p> <p>By default, when you start ES-DE for the first time, it will ask you to create game folders for different systems, then exit so you can populate those folders with actual games. Do this, then additionally, create a folder called &quot;Apps&quot; in the folder you chose (typically <code>~/ROMs</code>).</p> <p>You can now make a custom system in ES-DE, by creating the following file:</p> <p><code>~/.emulationstation/custom_systems/es_systems.xml</code>:</p> <pre><code>&lt;systemList&gt; &lt;system&gt; &lt;fullname&gt;Apps&lt;/fullname&gt; &lt;name&gt;Apps&lt;/name&gt; &lt;path&gt;~/ROMs/Apps&lt;/path&gt; &lt;extension&gt;.sh .SH .py .PY&lt;/extension&gt; &lt;command&gt;open -a &quot;%ROM%&quot;&lt;/command&gt; &lt;platform&gt;apps&lt;/platform&gt; &lt;theme&gt;esconfig&lt;/theme&gt; &lt;/system&gt; &lt;/systemList&gt; </code></pre> <p>As you can see, this is pretty straight-forward. This will make a custom system named &quot;Apps&quot; in ES-DE, which gets its games from <code>~/ROMs/Apps</code>, and will look for files with <code>.sh</code> or <code>.py</code> file extensions. Save the file, and you can now make the simple scripts that go into this folder.</p> <p>For each app you want to launch from within ES-DE, create a <code>.sh</code> file in <code>~/ROMs/Apps</code> and make them executable (<code>chmod u+x script.sh</code>. Here are mine:</p> <p><code>Kodi.sh</code>:</p> <pre><code>#!/usr/bin/env sh open -a &quot;Kodi&quot; </code></pre> <p><code>Moonlight.sh</code>:</p> <pre><code>#!/usr/bin/env sh open -a &quot;Moonlight&quot; </code></pre> <p><code>YouTube.sh</code>:</p> <pre><code>#!/usr/bin/env sh open &quot;https://www.youtube.com/feed/subscriptions?&quot; </code></pre> <p>As you can see, these are simple as. The YouTube script opens your default browser to your subscriptions page on YouTube, but you can just as easily configure it to launch in a specified one, by using <code>open -a &quot;Firefox&quot; &quot;https://www.youtube.com/&quot;</code> if you want.</p> <p>Save all files, restart ES-DE, and you should have a new category named Apps, containing your scripts. Launching the menu item will launch and give input focus to the app in question. Then use your gamepad/USB remote/whatever to navigate the given app. ES-DE and Moonlight works with gamepad navigation, but I've found that Kodi on macOS is hit or miss when it comes to this. It's worked a few times, but then stopped working, so I just use my remote for that.</p> <h1 id="post-13-closing-thoughts">Closing thoughts</h1> <p>A USB remote is usually a simple and cheap device, and can be found on eBay or AliExpress simply by searching for &quot;usb pc remote control&quot;. <a href="proxy.php?url=https://s.click.aliexpress.com/e/_DlaoiNn">Here is the one I use</a>, which works great and costs next to nothing. This one has a button to switch between mouse mode and remote mode, which is important as you can't always navigate around macOS using the remote functionality alone.</p> <p>There's lots of fun to be had in bed.... ;) Making Apple products an integral part of my bedroom experience isn't something I thought I would do, but it works surprisingly well. Now I have an all-in-one solution for gaming, movies, TV shows and YouTube content, all in one box, complete with uBlock to block YouTube ads, and SponsorBlock to skip sponsor segments!</p> <p>For an extra smooth experience, you can configure macOS to open ES-DE when you log in by right clicking its dock icon and choosing Options &gt; Open at login, and also making sure to uncheck &quot;Restore windows [...]&quot; whenever you log out/shut down the computer. I've also set both Kodi and the Mac to go to sleep after an hour of inactivity, so if I fall asleep, it won't run and use power needlessly throughout the night.</p> automation dualshock dualshock-4 gaming kodi mac macos playstation streaming youtube ntfy: Send notifications through libnotify to Linux desktop https://datalars.com/id/post/12/ Mon, 17 Apr 2023 16:51:07 +0200 https://datalars.com/2023/04/17/ntfy-send-notifications-through-libnotify-to-linux-desktop/ <p>I've recently started using <a href="proxy.php?url=https://ntfy.sh/">ntfy</a> to send notifications to my phone from some scripts I'm running on my home NAS. This works great, but when I'm on my PC, I'd rather get notifications there instead of on the phone. There doesn't seem to be a desktop app for ntfy, but luckily the API is extremely simple. I've also recently started picking up Python, so I decided to whip together a simple ntfy notification delivery system for the Linux desktop. To use this, you need <code>notify-send</code> (provided by the <code>libnotify</code> package), and <code>python3</code>.</p> <p><code>ntfy-listener.py</code>:</p> <pre><code>from sys import argv import requests import json import subprocess # Sends desktop notifications to a subscribed ntfy topic through libnotify/notify-send # Usage: python3 ntfy-listener.py topic-name if len(argv) &gt; 1: try: resp = requests.get(f&quot;https://ntfy.sh/{argv[1]}/json&quot;, stream=True) for line in resp.iter_lines(): if line: ntfyData = json.loads(line) if ntfyData[&quot;event&quot;] == &quot;message&quot;: ntfyTitle = &quot;ntfy&quot; if &quot;title&quot; in ntfyData: ntfyTitle = ntfyData[&quot;title&quot;] subprocess.run([&quot;notify-send&quot;, &quot;-u&quot;, &quot;normal&quot;, ntfyTitle, ntfyData[&quot;message&quot;]]) except KeyboardInterrupt: exit() except Exception as e: print(e) </code></pre> <p>Launch the script with <code>python3 ntfy-listener.py ntfy-topic-name</code> , where <code>ntfy-topic-name</code> is the ntfy topic you'd like to subscribe to, and any incoming notifications will be delivered though your DE's notification system! I've additionally added it to KDE's autostart, so it loads in the background when I log in:</p> <p><code>~/.config/autostart/ntfy-listener.desktop</code>:</p> <pre><code>[Desktop Entry] Exec=python3 /opt/scripts/python/ntfy-listener.py topic-name Name=ntfy-listener StartupNotify=true Terminal=false Type=Application </code></pre> automation linux notification notifications ntfy programming python script KDE Shenanigans: Playing a random video from Dolphin https://datalars.com/id/post/11/ Wed, 01 Mar 2023 01:10:05 +0100 https://datalars.com/2023/03/01/kde-shenanigans-playing-a-random-video-from-dolphin/ <p>Dolphin, the KDE file manager, is great, and has grown to become my favorite file manager of all time. It's super customizable, and a joy to use, which is more than I can say for the Windows equivalent. I do a fair amount of file management, so having a good tool for this is important, and when it's extensible like Dolphin, that's when it really starts to shine.</p> <p>I recently got the idea to make a script that will play a random video file from a given directory tree. Some possible use cases for this is to play a random episode of a TV show, or a random home recording stored on your computer. Making the script itself was fairly straight-forward, but I don't want to open up the terminal to launch my script every time I want to use it, and I have enough keyboard shortcuts for things already (the most important one being Meta+Z, which plays a rimshot sound effect, much to the amusement of everyone I know).</p> <p>Naturally, I started looking into integrating this into Dolphin. Initially, I wanted to make a custom toolbar button, but it turns out that isn't possible. What you can do however, is make a <em>KDE Service Menu</em>! These live in the context menu that pops up whenever you right-click things. They are really easy to create as well, you just pop a suitable .desktop file in the right directory, make it executable, and presto! You got yourself a context menu item! Let's see how to accomplish this.</p> <h2 id="post-11-making-the-script">Making the script</h2> <p>First of all, let's make the script itself. There are many ways to go about this, and I just went with the most straight-forward way I could think of; recursively reading the files of the current directory, filtering them on extension, and picking a random one out of the list.</p> <p><code>playrandomvideo.sh:</code></p> <pre><code>#!/bin/bash shopt -s nullglob nocasematch matches=$(find . -print | grep -i -E &quot;\.(webm|mkv|ogv|mov|avi|qt|ts|wmv|mp4|m4v|mpg|mp2|mpeg|mpe|mpv|flv)$&quot; --color=none) IFS=$'\n' read -r -d '' -a matchedFiles &lt;&lt;&lt; &quot;$matches&quot; numFiles=${#matchedFiles[@]} if [[ &quot;$numFiles&quot; -gt &quot;0&quot; ]] ; then rand=$((0 + $RANDOM % $numFiles)) randFile=${matchedFiles[${rand}]} xdg-open &quot;$randFile&quot; exit 0 else kdialog --sorry &quot;No videos found in the current directory tree.&quot; exit 1 fi </code></pre> <p><em>Note that if you use some esoteric video format that is not included in the regex pattern on line 3 of the script, you can just add it. You can also replace the list of file extensions entirely if you want to adapt the script to opening a different type of content; why not live life on the cutting edge and replace it with <code>ppt|pptx|odp</code>, so the next time you have a presentation at work, you won't know what you're presenting until you start it? Way to keep yourself on your toes.</em></p> <p>Place it somewhere safe, like <code>/opt/scripts</code>, and make it executable with <code>chmod +x playrandomvideo.sh</code>.</p> <h2 id="post-11-making-the-service-menu">Making the service menu</h2> <p>Prior to doing this, I didn't know how to create service menus, but KDE has <a href="proxy.php?url=https://develop.kde.org/docs/apps/dolphin/service-menus/">great documentation on how to do that</a>.</p> <p>First, find the location of service menus on your system, and <code>cd</code> into it. Create <code>playrandomvideo.desktop</code>, and make it executable.</p> <pre><code>$ qtpaths --locate-dirs GenericDataLocation kio/servicemenus /usr/share/kio/servicemenus $ cd /usr/share/kio/servicemenus $ sudo touch playrandomvideo.desktop $ sudo chmod +x playrandomvideo.desktop </code></pre> <p><em>Note that if your path is in your home directory, you do not need to use <code>sudo</code> to <code>touch</code> and <code>chmod</code> the file.</em></p> <p>Now open the file in your favourite text editor, and populate it with the following:</p> <p><code>playrandomvideo.desktop:</code></p> <pre><code>[Desktop Entry] Type=Service MimeType=inode/directory; Actions=playRandomVideoFromHere X-KDE-Priority=TopLevel [Desktop Action playRandomVideoFromHere] Name=Play random video from here Icon=media-playback-start Exec=cd &quot;%u&quot;; /opt/scripts/playrandomvideo.sh </code></pre> <p>Change the contents of the last line to match where you placed the script we made earlier.</p> <p>The line <code>X-KDE-Priority=TopLevel</code> is optional. If you keep it, the context menu entry will appear at the top level of the context menu, like so:</p> <p><img src="proxy.php?url=https://datalars.com/includes/thumbnail.php?file=toplevel2.png" alt="" loading="lazy"></p> <p>If you omit the line, the context menu item will live under a submenu named &quot;Actions&quot;:</p> <p><img src="proxy.php?url=https://datalars.com/includes/thumbnail.php?file=actionsmenu.png" alt="" loading="lazy"></p> <h2 id="post-11-done">Done!</h2> <p>Now you can right click any folder, or any empty area of the current folder, and click &quot;Play random video from here&quot; to do just that. The video will open in your system default handler for its respective file type (using <code>xdg-open</code>). If no videos are found, you'll be notified via a dialog box.</p> automation bash dolphin kde linux programming script video Sending arbitrary files directly from Firefox to your phone https://datalars.com/id/post/8/ Thu, 12 Jan 2023 16:44:22 +0100 https://datalars.com/2023/01/12/sending-arbitrary-files-directly-from-firefox-to-your-phone/ <h1 id="post-8-the-task">The task</h1> <p>Automation is great. There's just something inherently beautiful about the process of stringing together a bunch of software, services, or tools to attain a simple goal, and finding a solid solution that just works™. One automation task I've been tinkering with lately is how to send an arbitrary file directly from my browser to my phone, with as little fuss as possible. I often browse reddit or just the web in general and find a funny video or image I want to keep on my phone to send to someone, or just to easily refer to back later. If I can just click a button and nearly immediately have a copy of the resource in question available on my phone, that would be really swell.</p> <p>Luckily, the world of open source software provides a multitude of ways to accomplish this task; here's how I did it.</p> <h1 id="post-8-the-requirements">The requirements</h1> <p>To follow along at home, you'll need:</p> <ul> <li>A Linux-based computer</li> <li>An Android-based smartphone</li> <li><a href="proxy.php?url=https://www.getfirefox.com/">Firefox</a> on your PC</li> <li>The <a href="proxy.php?url=https://addons.mozilla.org/en-US/firefox/addon/open-with/">Open With</a> addon for Firefox</li> <li><a href="proxy.php?url=https://github.com/yt-dlp/yt-dlp">yt-dlp</a> (or youtube-dl or any of its forks) on your PC</li> <li>KDE Connect on your PC (ships with the KDE Plasma desktop, or can be installed on most other DEs through your package manager)</li> <li><a href="proxy.php?url=https://play.google.com/store/apps/details?id=org.kde.kdeconnect_tp">KDE Connect</a> on your phone</li> <li>Optional: <code>libnotify</code> for notifications, <code>pulseaudio</code> for audio alerts</li> </ul> <h1 id="post-8-the-solution">The solution</h1> <p>First, install the Open With addon into Firefox. Once that's done, follow the instructions it gives to set it up, it requires a helper script to be able to launch external resources from within Firefox. Install the KDE Connect app on your phone, and pair it with your computer. Now that that's set up, you can make a couple of scripts that the Firefox addon will run whenever you invoke it. The first one is specifically for video content, the second is for files.</p> <p><code>send-to-phone-yt-dlp.sh</code>:</p> <pre><code>#!/bin/bash deviceName=&quot;Fold 3&quot; ytdlpPath=&quot;/opt/yt-dlp&quot; savePath=&quot;/home/lars/Downloads/%(title)s [%(id)s].%(ext)s&quot; errorSound=&quot;/usr/share/sounds/ubuntu/notifications/Slick.ogg&quot; successSound=&quot;/usr/share/sounds/ubuntu/notifications/Positive.ogg&quot; notify-send -u low &quot;yt-dlp&quot; &quot;Starting download with yt-dlp...&quot; --icon=camera-video ytdlpOutput=$($ytdlpPath -o &quot;$savePath&quot; &quot;$1&quot; 2&gt;&amp;1) if [[ &quot;$?&quot; -gt 0 ]] ; then ytdlpOutput=$(echo $ytdlpOutput | tail -n1) notify-send -u normal &quot;Error&quot; &quot;${ytdlpOutput}&quot; --icon=emblem-warning paplay $errorSound else notify-send -u normal &quot;Success&quot; &quot;Download successful! ($1)&quot; --icon=emblem-success fileNameResult=$($ytdlpPath --get-filename -o &quot;$savePath&quot; &quot;$1&quot;) kdeconnect-cli -n &quot;$deviceName&quot; --share &quot;$fileNameResult&quot; paplay $successSound fi </code></pre> <p><code>send-to-phone-wget.sh</code>:</p> <pre><code>#!/bin/bash deviceName=&quot;Fold 3&quot; saveDir=&quot;/home/lars/Downloads&quot; errorSound=&quot;/usr/share/sounds/ubuntu/notifications/Slick.ogg&quot; successSound=&quot;/usr/share/sounds/ubuntu/notifications/Positive.ogg&quot; notify-send -u low &quot;Download&quot; &quot;Starting download with wget...&quot; --icon=unknown cd $saveDir dlFilename=$(wget &quot;$1&quot; 2&gt;&amp;1 | grep Saving | cut -d ' ' -f 3 | sed -e 's/[^A-Za-z0-9._-]//g') if [[ &quot;$?&quot; -gt 0 ]] ; then notify-send -u normal &quot;Error&quot; &quot;Download failed!&quot; --icon=emblem-warning paplay &quot;$errorSound&quot; else notify-send -u normal &quot;Success&quot; &quot;Download successful! ($1)&quot; --icon=emblem-success kdeconnect-cli -n &quot;$deviceName&quot; --share &quot;$dlFilename&quot; paplay &quot;$successSound&quot; fi </code></pre> <p>You'll need to do some changes to these scripts depending on your environment:</p> <ul> <li>Change the value of <code>deviceName</code> to the registered name of your phone in KDE Connect</li> <li>Change the value of <code>ytdlpPath</code> to point to the yt-dlp binary on your system</li> <li>Change the value of <code>savePath</code> to point to your preferred save location and filename of the videos downloaded by yt-dlp</li> <li>Change the value of <code>saveDir</code> to point to your preferred save directory of the files downloaded by wget</li> <li>Change the value of <code>errorSound</code> and <code>successSound</code> to the appropriate paths if you are not running a flavour of Ubuntu, or remove them altogether if you do not want audio feedback. In that case, remove all lines starting with <code>paplay</code> as well</li> <li>Replace the lines starting with <code>paplay</code> with appropriate commands for your audio system if you do not use PulseAudio, but still want audio feedback</li> <li>Remove the lines starting with <code>notify-send</code> if you do not want notifications or if you don't have <code>libnotify</code> installed</li> </ul> <p>Don't forget to make the scripts executable! (<code>chmod u+x /path/to/script.sh</code>). Place them somewhere safe, i like <code>/opt/scripts</code>.</p> <p>The next step is adding these scripts inside the Open With addon for Firefox. Click the Open With button in the toolbar, and click &quot;Open With options&quot;. Click &quot;Add browser&quot;. Fill in a name, and the path to the script with <code>&quot;%s&quot;</code> at the end, this is replaced with the URL when the script is invoked. Pick a custom icon if you'd like.</p> <p><img src="proxy.php?url=https://datalars.com/includes/thumbnail.php?file=2023-01-1218-10.png" alt="" loading="lazy"></p> <p>Repeat the same process for the other script, and you should end up with these two entries:</p> <p><img src="proxy.php?url=https://datalars.com/includes/thumbnail.php?file=2023-01-1218-20.png" alt="" loading="lazy"></p> <p>And that's really all there is to it. Now, whenever you are on a page that has a video you want to download and send to your phone, you can click the Open With toolbar icon, then &quot;Send video to phone&quot;. If you're viewing a file, click the corresponding Open With item. This also works for links; If there's a link to an image or a file you want to download and send to your phone, just right click the link, go to &quot;Open With&quot;, and click &quot;Send file to phone (wget)&quot;, or pick the corresponding option if the link is to a video page.</p> <h1 id="post-8-closing-thoughts">Closing thoughts</h1> <p>Being able to send any video, picture or arbitrary file to my phone in two clicks is really convenient! The Open With addon is also really great for automating many other tasks that involve URLs, here are a couple of examples:</p> <ul> <li>If a page doesn't behave/work in Firefox, I wanna open it in another browser. I have the Flatpak version of Ungoogled Chromium installed for that, but opening that, manually copying the URL from FF, tabbing over to Chromium, then pasting it in the address bar is a chore. Just add it to Open With: <code>flatpak run com.github.Eloston.UngoogledChromium %s</code>, and two clicks will open your current URL in the other browser (Note that this will NOT work if Firefox is running as a Flatpak, as <code>flatpak run</code> executed from within another flatpak will silently fail, in my experience, even with full permissions).</li> <li>If I wanna send a link to JDownloader instead of opening it in Firefox, I can just add JDownloader to Open With, with the command <code>/bin/sh /opt/jd2/JDownloader2 %s</code></li> </ul> <p>I'm sure there are many other uses for this approach as well, get creative!</p> automation bash kde-connect linux programming script yt-dlp qBittorrent v4.5.0: The Hitchhiker's Guide to Legible Text https://datalars.com/id/post/5/ Tue, 03 Jan 2023 11:08:27 +0100 https://datalars.com/2023/01/03/qbittorrent-v450-the-hitchhikers-guide-to-legible-text-2/ <p><em>The story so far: In the beginning, qBittorrent was created. Then they released v4.5.0. This has made a lot of people very angry and been widely regarded as a bad move.</em></p> <p><small>(If you just want the theme file with completely white text colors, you can download that <a href="proxy.php?url=https://multiup.org/c9206ef22c7a2fc9334a8dafe447b8f4">here</a>. Place it somewhere safe, then open qBT, and go to Tools &gt; Preferences &gt; Behaviour and check the checkbox for &quot;Use custom UI theme&quot;. Then browse to the theme file, click OK, and restart qBT)</p> <p>Update 2023-03-22: ZippyShare is shutting down, so the link now points to MultiUp, which uploads to multiple services.</small></p> <h1 id="post-5-the-problem">The problem</h1> <p>The very bad move in this case, was hard-coding foreground colours, while simultaneously <em>not</em> hard-coding background colours. Most, if not all, operating systems in use today will let you choose a theme for your apps, so you can probably see how this quickly becomes a problem. If your app's hard-coded foreground colour has poor contrast with the user's chosen background colour, the user is gonna have a bad time. Sure, they can change their background colour by changing their theme, but why should the user be forced to change their whole system theme because of one app that disregards user choice? So when the eminent qBT team decided to hard-code only one of these, anyone who uses a dark theme in their OS, immediately got problems.</p> <p>I am a proud KDE user, and like any proud basement-dwelling nerd, I use a dark theme. This dark theme isn't even an obscure, home-brewed one, it is Breeze Dark, which ships with KDE. This works exceptionally well, and disregarding the odd Java app, it works for <em>all apps</em>, mostly regardless of the UI toolkit used to make them. GTK apps, check. Qt apps, check.</p> <p>But wait... qBittorrent is a Qt app, right? That's right, qBT is built with Qt on all platforms. This is great both for the developers who only have to deal with one toolkit, and for the users who can expect a more or less consistent experience across platforms.</p> <p>Now let's move on to the evidence phase. Consider the following. This is a screenshot of qBitTorrent v4.3.9 (or as I like to call it, &quot;pre-<em>fuckening</em>&quot;):</p> <p><img src="proxy.php?url=https://datalars.com/includes/thumbnail.php?file=screenshot20230103112556.png" alt="" loading="lazy"></p> <p>Wow, so legible! White text on a dark gray background has really good contrast, and makes the text stand out, so it's super readable. If I switch to the normal Breeze theme, the background will turn white, and the text will turn black. So logical! It respects my global OS theme! Yay!</p> <p>But then, through the magic of <code>sudo pkcon update</code> and restarting my computer, on the next launch of qBT, I am met with this horrible sight:</p> <p><img src="proxy.php?url=https://datalars.com/includes/thumbnail.php?file=2023-01-0311-43.png" alt="" loading="lazy"></p> <p>I'm not gonna lie; when I saw this, I let out an audible &quot;what the fuck is this?&quot;. Like a lot of people, I have astigmatism and am near-sighted. If you sit right next to your monitor and have 20/20 vision, then yeah, sure, you might be able to read this. But the fact is, <a href="proxy.php?url=https://www.who.int/news-room/fact-sheets/detail/blindness-and-visual-impairment">over a fourth of the global population currently has some sort of visual impairment</a>, and if they live long enough, <a href="proxy.php?url=https://www.lasereyesurgeryhub.co.uk/data/visual-impairment-blindness-data-statistics/">literally <strong>everyone</strong> will develop a vision impairment during their lifetime</a>. The contrast is non-existent; dark blue against a dark gray (or black) background is absolute dogshit for legibility.</p> <p>Surely, when they decided that <em>L'ard-core Deep Bleu</em> is the new default text colour for everyone, someone must've chimed up with something along the lines of <em>&quot;but let it be optional&quot;</em> or <em>&quot;let's at least include a colour picker or an option to revert to theme-default colours&quot;</em>. No. Of course not. That would be dumb and they would not get invited to the proverbial open-source Christmas party.</p> <p>Put briefly: <strong>A normal end user cannot do anything about this without significant effort</strong>. No option to revert, no option to change colours, no option to ignore built-in theming and use the OS theme. A normal end user that now cannot read their app anymore will either a) uninstall and use an alternative client, b) downgrade to a previous version, or c) try to find a workaround. All of which are bad for UX. A user should not be forced to downgrade or replace the app in order to read basic, informational text.</p> <h1 id="post-5-the-solution">The solution</h1> <p>Some web searching will reveal that there are custom themes available for qBT. And that is an okay workaround. The problem is, all these custom themes change the whole look of the application, and thus also ignore the user's system defined theme. But also, v4.5.0 broke most of these custom themes. So, what do we do about this? After trawling through some poorly documented ways of creating themes, I was finally able to make a simple one that only changes the text colors, nothing else. Here is how I did it.</p> <p>I started <a href="proxy.php?url=https://github.com/qbittorrent/qBittorrent/wiki/Create-custom-themes-for-qBittorrent">here</a>. Which has an okay-ish explanation of how themes work and how the markup is written, but not much info about how to make the actual theme file, until you find the link to <a href="proxy.php?url=https://github.com/jagannatharjun/qbt-theme/blob/master/Builds/make-resource.py">this</a>, which is made, quote, &quot;for the easy creation of .qbttheme files&quot;. So sure, I download the Python script and attempt to run it, only to be barraged by error messages about missing resources.</p> <p>I think &quot;whatever&quot; and start working on the actual code. I read through the documentation which tells me <code>stylesheet.qss</code> is required and tells me a bunch of the rules to put in there, but at the end tells me &quot;jk lol disregard all that and put this in your config file instead&quot;. What a waste of time. I make an empty <code>stylesheet.qss</code> and populate <code>config.json</code> with the following:</p> <pre><code>{ &quot;colors&quot;: { &quot;TransferList.Downloading&quot;: &quot;#FFFFFF&quot;, &quot;TransferList.StalledDownloading&quot;: &quot;#FFFFFF&quot;, &quot;TransferList.DownloadingMetadata&quot;: &quot;#FFFFFF&quot;, &quot;TransferList.ForcedDownloading&quot;: &quot;#FFFFFF&quot;, &quot;TransferList.Allocating&quot;: &quot;#FFFFFF&quot;, &quot;TransferList.Uploading&quot;: &quot;#FFFFFF&quot;, &quot;TransferList.StalledUploading&quot;: &quot;#FFFFFF&quot;, &quot;TransferList.ForcedUploading&quot;: &quot;#FFFFFF&quot;, &quot;TransferList.QueuedDownloading&quot;: &quot;#FFFFFF&quot;, &quot;TransferList.QueuedUploading&quot;: &quot;#FFFFFF&quot;, &quot;TransferList.CheckingDownloading&quot;: &quot;#FFFFFF&quot;, &quot;TransferList.CheckingUploading&quot;: &quot;#FFFFFF&quot;, &quot;TransferList.CheckingResumeData&quot;: &quot;#FFFFFF&quot;, &quot;TransferList.PausedDownloading&quot;: &quot;#FFFFFF&quot;, &quot;TransferList.PausedUploading&quot;: &quot;#FFFFFF&quot;, &quot;TransferList.Moving&quot;: &quot;#FFFFFF&quot;, &quot;TransferList.MissingFiles&quot;: &quot;#FFFFFF&quot;, &quot;TransferList.Error&quot;: &quot;#FFFFFF&quot; } } </code></pre> <p>As you can probably tell, I copy pasted the list from the documentation and made all the text colours pure white. That's the only change I want, because it's the only forced change that made a huge difference to someone who can't tell a frog from a lawn chair, without glasses.</p> <p>Great! Now I have a zero-byte <code>stylesheet.qss</code> and a <code>config.json</code> with some actual changes in it. Let's get it packed up into a <code>.qbtheme</code>!</p> <p>Oh, right. The python script spat out a bunch of errors. I don't know Python, but I know other programming languages, and I'm generally able work my way around this sort of stuff. Apparently the errors are because you don't need just this script, you need to clone the whole repository, which isn't mentioned anywhere. Fine, one <code>git clone https://github.com/jagannatharjun/qbt-theme</code> later, I have a directory full of god knows what. I <code>cd</code> to the right directory and try again, with the following syntax:</p> <pre><code>python make-resource.py -style stylesheet.qss -config config.json </code></pre> <p>But in return, I get this:</p> <pre><code>/Builds/tools/rcc -binary -o style.qbtheme resources.qrc Traceback (most recent call last): File &quot;make-resource.py&quot;, line 80, in &lt;module&gt; if not subprocess.call(cmd): File &quot;/usr/lib/python2.7/subprocess.py&quot;, line 172, in call return Popen(*popenargs, **kwargs).wait() File &quot;/usr/lib/python2.7/subprocess.py&quot;, line 394, in __init__ errread, errwrite) File &quot;/usr/lib/python2.7/subprocess.py&quot;, line 1047, in _execute_child raise child_exception OSError: [Errno 2] No such file or directory </code></pre> <p><em>(Note the beautifully named python function <strong>_execute_child</strong>, which is coincidentally what I want to do when I have to spend time on debugging code for a language I don't know)</em></p> <p>Right. Line 80 it says. Line 80 of <code>make-resource.py</code> reads as follows:</p> <pre><code>if not subprocess.call(cmd): </code></pre> <p>Clearly, <code>cmd</code> is a variable, which is coincidentally defined a few lines above, on line 77:</p> <pre><code>cmd = [os.path.join(os.path.dirname(os.path.realpath(__file__)), 'tools/rcc'), '-binary', '-o', args.output, 'resources.qrc'] </code></pre> <p>What I gather from this, is that the script is attempting to call a binary located at <code>tools/rcc</code>. But the error Python spits out is <code>No such file or directory</code>, do I have to supply this mysterious binary myself? Huh? That's when I decide to look inside the <code>tools</code> directory.</p> <pre><code>$ ls -lah total 1,1M drwxrwxr-x 2 lars lars 4,0K Jan 3 12:34 . drwxrwxr-x 8 lars lars 4,0K Jan 3 12:35 .. -rw-rw-r-- 1 lars lars 1,1M Jan 3 12:34 rcc.exe </code></pre> <p><strong><code>rcc.exe</code></strong>. Real <em>bruh</em> moment. Instead of checking for OS or giving a useful error message, this repository bundles a Windows binary of <code>rcc</code>. But what is <code>rcc</code> I wonder? A web search tells me that rcc stands for Renal cell carcinoma, and I'm sure if I keep reading, I'll find out I have it. I have a big brain moment and add &quot;qt&quot; to the search, and find out that RCC is the Qt Resource Compiler. That makes sense, and I probably have this somewhere already since I run KDE, right?</p> <pre><code>$ whereis rcc rcc: /usr/bin/rcc </code></pre> <p>Yay, I already have it installed on my system! I change line 77 of <code>make-resource.py</code> to:</p> <pre><code>cmd = ['rcc', '-binary', '-o', args.output, 'resources.qrc'] </code></pre> <p>I save <code>make-resource.py</code> as a new file, then run it again with the appropriate arguments, and <em>voilà!</em> It works!</p> <pre><code>$ python make-resource-linux.py -style stylesheet.qss -config config.json adding ./make-resource-linux.py adding ./config.json adding ./stylesheet.qss [] rcc -binary -o style.qbtheme resources.qrc resources.qrc: Warning: potential duplicate alias detected: 'stylesheet.qss' resources.qrc: Warning: potential duplicate alias detected: 'config.json' </code></pre> <p>For some reason the script added itself to the resource file, but whatever. I save the resulting <code>.qbtheme</code>-file as <code>~/.local/bin/style.qbtheme</code> for safekeeping, then I apply the theme in qBittorrent.</p> <p>Tada! It works!</p> <p><img src="proxy.php?url=https://datalars.com/includes/thumbnail.php?file=2023-01-0313-09.png" alt="" loading="lazy"></p> <p>Doesn't that look just positively <em>lovely</em>?</p> <h1 id="post-5-the-conclusion">The conclusion</h1> <p>Now, this task, <strong>making previously white text white again</strong>, took me, a technical person, a non-negligible amount of time to figure out (on the magnitude of an hour or two). How is it expected that a normal, non-technical end user is supposed to accomplish this same task before the heat death of the universe? Why do they have to in the first place?</p> <p>Please, for the love of all that is good and decent in this world, the next time you force a visual change upon users, include a colour picker option to let the user override your choices, or, you know, respect the system theme. I appreciate that qBT is free and open-source software and that resources are limited, but this is UX 101. If you define one colour, you have to define <em>all</em> colours. The best is to define none and let the user decide. Don't get me wrong, credit where credit is due: I love qBittorrent with a passion, and it is one of my single most-used pieces of software. Functionally, it is <em>fantastic</em>. That being said, hard-coding colours was a bad move.</p> <p>You can download the resulting all-white-text <code>.qbtheme</code>-file <a href="proxy.php?url=https://www71.zippyshare.com/v/HywJ7uYu/file.html">here</a>. Place it somewhere safe, then open qBT, and go to Tools &gt; Preferences &gt; Behaviour and check the checkbox for &quot;Use custom UI theme&quot;. Then browse to the theme file, click OK, and restart qBittorrent.</p> design linux programming python qbittorrent Bash script: Randomize filenames in a directory https://datalars.com/id/post/3/ Sun, 01 Jan 2023 20:23:18 +0100 https://datalars.com/2023/01/01/bash-script-randomize-filenames-in-a-directory/ <p>Continuing on the theme of file management from my last post: This script takes a folder of files and randomizes all filenames, whilst keeping the filename extension. This is useful if you're sorting by name, and want to have the files presented in a random order. Some possible use cases are a folder of pictures you intend to post to a blog, do further processing on, and the order and names of the files aren't important.</p> <p><strong>Usage</strong>: <code>cd</code> to the directory that contains the files you wish to randomize the filenames of, then run the script.</p> <p><code>randomize_filenames.sh</code>:</p> <pre><code>#!/bin/bash # Randomize file names in current working directory, keeping the filename extension # Modified from: https://unix.stackexchange.com/a/6553 # Ignores dotfiles and subdirectories find . -type f -not -path '*/.*' | while read -r name; do ext=${name##*/} case $ext in *.*) ext=.${ext##*.};; *) ext=;; esac newName=`mktemp --dry-run XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX` mv &quot;$name&quot; &quot;$newName$ext&quot; done </code></pre> bash linux programming script Bash script: Sort a large directory into alphabetical subdirectories https://datalars.com/id/post/2/ Wed, 28 Dec 2022 21:24:50 +0100 https://datalars.com/2022/12/28/bash-sort-large-directory-into-alphabetical-subdirectories/ <p>Every so often, I download something off the internet that is delivered as a multitude of files in a root folder, with no kind of sorting applied to it. Big sets of games for the PICO-8 engine, perhaps, or an assorted collection of music files. Often times these sets, especially if they are games, will go onto an embedded device like a modified games console, a flash cart, or onto an emulation handheld, which makes browsing 7349 files a nightmare using only a D-pad on a small screen. The device might not even support showing more than a specific number of files in one directory.</p> <p>The solution? This Bash script! The script will create folders named 0-9 and A through Z, and move all files that start with a number or letter into its respective folder. At the end, you'll end up with a clean directory structure you can copy to your storage media, and not have to flick through hundreds of pages just because you want to play something that starts with a W.</p> <p><strong>Usage</strong>: <code>cd</code> to the directory containing your unsorted files, then execute the script. Files that do not start with a number or letter will not be moved.</p> <p><code>foldersort.sh</code>:</p> <pre><code>#!/bin/bash shopt -s nocasematch dirs=(0-9 A B C D E F G H I J K L M N O P Q R S T U V W X Y Z) for file in * do for dir in &quot;${dirs[@]}&quot; do mkdir -p &quot;$dir&quot; if [[ $file =~ ^[$dir] ]] then mv &quot;$file&quot; &quot;$dir&quot; break fi done done </code></pre> bash linux programming script