<?xml version="1.0" encoding="UTF-8"?>
<?xml-stylesheet type="text/xsl" href="/assets/feed.xslt"?>
<rss version="2.0"
     xmlns:atom="http://www.w3.org/2005/Atom"
     xmlns:content="http://purl.org/rss/1.0/modules/content/"
     xmlns:wfw="http://wellformedweb.org/CommentAPI/">
<channel>
<title>Web logs of McSinyx</title>
<link>https://lumvok.store</link>
<atom:link href="https://lumvok.store/feed.xml" rel="self" type="application/rss+xml"/>
<description>Random write-ups packed with pop culture references</description>
<copyright><![CDATA[🄯 2019–2024 Nguyễn Gia Phong under CC BY-SA 4.0]]></copyright>
<language>en</language>
<generator>Franklin</generator>
<item>
  <title>Best Ways to Watch YouTube Videos</title>
  <link>https://lumvok.store/blog/youtu/index.html</link>
  <guid>https://lumvok.store/blog/youtu/index.html</guid>
  <description>Do you know de wey?  Lemme show you de wey&#33;</description>
  <category>fun</category><category>recipe</category><category>net</category><category>nix</category><category>clipboard</category>
  <pubDate>Wed, 17 Jan 2024 00:00:00 +0000</pubDate>
  <content:encoded><![CDATA[
<h1 id="best_ways_to_watch_youtube_videos">Best Ways to Watch YouTube Videos</h1>
<p>In today&#39;s episode of <a href="https://www.alexmolas.com/2023/07/15/nobody-cares-about-your-blog.html">guides nobody asked for and likely having been covered by someone more qualified</a>, lemme show you the <em>correct</em> ways to view videos hosted on YouTube and other hostile, tracker-riddled hellscapes.  Whilst I despise Google&#39;s mass surveillance practices, it stores a large proportion of culturally significant videos and clips that would be difficult to mirror to user-respecting services due to copyright.  Hell, even YouTube doesn&#39;t have the right to distribute many of them in the first place.</p>
<p>Because of YouTube&#39;s circumvention of advertisement blockers, the ad-blocking arm race finally caught mainstream media attention and tis kool to talk about that now.  Hence I&#39;m happy to jump on the bandwagon, albeit a wee bit late, but this ain&#39;t just that. Since I feed you poison—over 4&#37; of the pages linked from my site are on YouTube—the least I can do is sell you my cures.</p>
<h2 id="using_a_proper_media_player">Using a Proper Media Player</h2>
<p>The most popular solutions are either to use for a good blocker on a browser with &#40;supposedly&#41; long-term support for <a href="https://github.com/uBlockOrigin/uBlock-issues/issues/338#issuecomment-1332300191">Manifest V2</a> like <a href="https://ublockorigin.com">uBlock Origin</a> on Firefox, or use alternative front-ends such as <a href="https://invidious.io">Invidious</a> or <a href="https://github.com/TeamPiped/Piped">Piped</a>.  Although uBlock Origin is essential for a pleasant experience on the modern interwebs and alternative frontends offers the best UX for browsing videos, in-browser and service-specific media players are inferior anyway when compared to programs properly designed for a decent playback experience.</p>
<p>My favorite has been <a href="https://mpv.io">mpv</a> for as long as I can remember, as it makes it easy to adjust video brightness/contrast/etc., playback speed, subtitle size and placements, and to overamplify quiet audios.  Out of the box, it integrates with <a href="https://github.com/yt-dlp/yt-dlp">yt-dlp</a>, a time shifter with support for <a href="https://github.com/yt-dlp/yt-dlp/raw/master/supportedsites.md">most online media services</a>. Just drop the URL into an mpv window and <a href="https://www.youtube.com/watch?v&#61;_FNzL5nW_u4">boom</a>, it werks&#33;</p>
<p>Either <a href="https://uno.starshard.studio/notes/9nmgldtdgghu8m2n">drag-and-drop</a> or invoking <code>mpv &#36;url</code> is quite convenient, but not that close to following an anchor, is it?  You&#39;d need to first open mpv or a program launcher<sup id="fnref:launch">[1]</sup>, then drag the URL there, or perhaps copy and paste it for the latter cases. What if you <a href="https://ziglang.org/perf">gotta go fast</a>, aye?  As a <a href="https://video.hardlimit.com/c/morbiwars">hedgehog-maxxer</a> meself, of course I can do better, and here&#39;s how.</p>
<h2 id="with_a_browser_add-on">With a Browser Add-on</h2>
<p>While drafting this article, I noticed that the <em>ff2mpv</em> extension I was using had <a href="https://github.com/woodruffw/ff2mpv/commit/2397193b36e6.patch">technically been non-free</a> for a while.  Albeit I understand and respect the author&#39;s noble intention against violence, I believe discrimination never ends up helping those oppressed due to the power imbalance for the exclusion false-positives to be worth it.</p>
<p>For this reason, I switched to <a href="https://addons.mozilla.org/en-US/firefox/addon/iina-open-in-mpv">Open in mpv</a> and recommend it instead. The usage is practically the same: open context menu at the video URL and select <em>Open this link in mpv</em>.  The internal mechanism is a bit different though, and because it influences the installation process, I will try to briefly explain <a href="https://www.youtube.com/watch?v&#61;1Fl2sMV7Hcc">how it works</a>.</p>
<p>The way <em>Open in mpv</em> works is a bit convoluted.  First, it wraps the specified URL in a <code>mpv</code> scheme.  The new URL starts with <code>mpv://</code> is then passed back to Firefox, which must have been configured to open it in the native program <code>open-in-mpv</code>.  This program parses the URL into the equivalent mpv command and execute it.  If you are not on NixOS, see the <a href="https://github.com/Baldomo/open-in-mpv/raw/master/README.md">extension&#39;s README</a> to set it up yourself.</p>
<p>Otherwise, it can be declared in <a href="https://nixos.org/manual/nixos/stable/options#opt-programs.firefox.policies">configuration.nix&#40;5&#41;</a> as follows. The declarations should be self-explanatory after referencing Firefox&#39;s documentation for <a href="https://mozilla.github.io/policy-templates">policies.json</a>.  If you have trouble finding an extension&#39;s ID and download URL, search for it in <a href="https://gnuzilla.gnu.org/mozzarella">Mozzarella</a>.</p>
<pre><code class="language-nix">&#123; pkgs, ... &#125;:
&#123;
  programs.firefox &#61; &#123;
    enable &#61; true;
    policies &#61; &#123;
      ExtensionSettings.&quot;&#123;d66c8515-1e0d-408f-82ee-2682f2362726&#125;&quot; &#61; &#123;
        default_area &#61; &quot;menupanel&quot;;
        installation_mode &#61; &quot;normal_installed&quot;;
        install_url &#61;
          &quot;https://addons.mozilla.org/firefox&quot;
          &#43; &quot;/downloads/latest/iina-open-in-mpv/latest.xpi&quot;;
      &#125;;
      Handlers.scheme.mpv &#61; &#123;
        action &#61; &quot;useHelperApp&quot;;
        ask &#61; false;
        handlers &#61; &#91; &#123;
          name &#61; &quot;open-in-mpv&quot;;
          path &#61; &quot;&#36;&#123;pkgs.open-in-mpv&#125;/bin/open-in-mpv&quot;;
        &#125; &#93;;
      &#125;;
    &#125;;
  &#125;;
&#125;</code></pre>
<p>Even though Mozzarella is supposed to only show libre add-ons, be aware that the metadata it crawls from <a href="https://addons.mozilla.org">addons.mozzila.org</a> might not always be <a href="https://issues.guix.gnu.org/68361">correct</a>.  Ideally, browser extensions should be packaged in the distribution&#39;s repository, but packaging discipline is not exactly NixOS&#39;s strong suit.  I will probably post an update on how to declare <code>policies.json</code> in Guix once I figure that out.</p>
<h2 id="from_a_feed_reader">From a Feed Reader</h2>
<p>Now we can properly watch videos while browsing the web, but subscribing to YouTube channels on its web interface would require creating an account and subjecting one&#39;s self to more surveillance.  Fortunately, at the time of writing, YouTube still provide Atom <a href="https://en.wikipedia.org/wiki/Web_feed">feeds</a> for syndication. Funny enough, they are advertised on the channel pages as RSS:</p>
<pre><code class="language-html">&lt;link rel&#61;&quot;alternate&quot;
      type&#61;&quot;application/rss&#43;xml&quot;
      title&#61;&quot;RSS&quot;
      href&#61;&quot;https://www.youtube.com/feeds/videos.xml?channel_id&#61;…&quot;&gt;</code></pre>
<p>The referenced feed employ <a href="https://www.rssboard.org/media-rss">Media RSS</a> to communicate the video URL. This extension is widely supported by feed readers, as well as the previously mentioned feed-discovery mechanism.  I use <a href="https://lzone.de/liferea">Liferea</a>, which allows me to directly paste the YouTube channel&#39;s URL<sup id="fnref:ytc">[2]</sup>, and displays each video&#39;s description, thumbnail and enclosed media, e.g.</p>
<p><img src="https://lumvok.store/assets/liferea-youtube.png" alt="Liferea in action" /></p>
<p>For each MIME type to, enclosures can be configured to be opened by a user-preferred program.  In this case, I set <code>mpv --ytdl-format&#61;b</code> for <code>application/x-shockwave-flash</code> &#40;a reminiscence of a time when browsers needed <a href="https://ruffle.rs">Flash</a> to play videos and animations&#41; for the <em>second</em> best quality to save some bandwidth.  YouTube encodes the highest resolution video separate from the audio, so the best combined format <code>b</code> is one level lower than yt-dlp&#39;s default best video and best audio together.</p>
<h2 id="via_clipboard_integration">Via Clipboard Integration</h2>
<p>People also share videos with me via instant messaging. I find it cumbersome to open the URL in the browser then redirect it to the media player, so the clipboard is used as the bridge instead. To do this, I simply create a key binding to the command below.<sup id="fnref:wl">[3]</sup></p>
<pre><code class="language-sh">mpv --ytdl-format&#61;b &quot;&#36;&#40;xclip -out -selection clipboard&#41;&quot;</code></pre>
<h2 id="musing">Musing</h2>
<p>There, I shared how I do it so <a href="https://lumvok.store/blog/youtu">you can too</a>&#33;  If they seem needlessly complex, you share my disappointment on the UX evolution of the mainstream web.  I dream of a more semantic web, not necessarily web 3.0, perhaps just more explicitly typed, where e.g. a YouTube URL for embedding would be a <code>video/webm</code> instead of a <code>text/html</code>.</p>
<p>If <code>mailto</code> URIs can launch our email client, and social media pages can bug us to open the post in their own app, why can&#39;t we have interoperable media handling?  Maybe we should, but I&#39;m not sure if we can. <a href="https://www.searchenginejournal.com/youtube-is-showing-ads-on-non-monetized-channels/388674">Greed</a> stands in our way.  Providers force us to use their proprietary <a href="https://pluralistic.net/2023/01/21/potemkin-ai">malware</a> to consume their service.  <a href="https://www.defectivebydesign.org">DRM</a> has become the foundation of media distribution.  Grassroots movements like <a href="https://framasoft.org">Framasoft</a> might never reach mainstream status.</p>
<p>I don&#39;t mean to tell you to give up though, just to direct your energy to where it matters.  Spend less on developing <a href="https://sr.ht/~benbusby/farside">alternative front-ends</a> than on ethical replacements, bridges and inviting people over. We need more <a href="https://sepiasearch.org">videos</a>, more <a href="https://www.funkwhale.audio">music</a>, more <a href="https://castopod.org">podcasts</a>, more <a href="https://en.wikipedia.org/wiki/Open_access">knowledge</a>, better <a href="https://xmpp.org">instant</a> <a href="https://matrix.org">messaging</a>, better <a href="https://seirdy.one/posts/2021/03/10/search-engines-with-own-indexes">search engines</a>, better <a href="https://browser.mt">translations</a>, better <a href="https://www.home-assistant.io">home</a> <a href="https://platypush.tech">automation</a>, and whatnot. Against all odds, maybe things will finally start to improve even for those outside of our bubble.  <a href="https://fe.disroot.org/@mcsinyx/posts/ALaW77HgCSPq4pLxpo">Perchance.</a></p>
<table class="fndef" id="fndef:launch">
    <tr>
        <td class="fndef-backref">[1]</td>
        <td class="fndef-content">Or a terminal emulator</td>
    </tr>
</table><table class="fndef" id="fndef:ytc">
    <tr>
        <td class="fndef-backref">[2]</td>
        <td class="fndef-content">Something starting with https://www.youtube.com/@</td>
    </tr>
</table><table class="fndef" id="fndef:wl">
    <tr>
        <td class="fndef-backref">[3]</td>
        <td class="fndef-content">On <a href="https://wayland.social/@compositor/110768798303454842">Wayland</a>, replace <code>xclip</code> with something equivalent</td>
    </tr>
</table>    <a href="mailto:cnx.site@loa.loang.net?In-Reply-To=%3Cblog/youtu@cnx%3E&Subject=Re: Best Ways to Watch YouTube Videos">Reply via email</a>]]></content:encoded>
  <comments><![CDATA[https://lists.sr.ht/~cnx/site?search=In-Reply-To:%3Cblog/youtu@cnx%3E]]></comments>
  <wfw:commentRss>https://lumvok.store/blog/youtu/comments.xml</wfw:commentRss>
</item>
<item>
  <title>Writing a Clipboard Manager</title>
  <link>https://lumvok.store/blog/threa/index.html</link>
  <guid>https://lumvok.store/blog/threa/index.html</guid>
  <description>Raku&#39;s concision demonstrated in form of a tutorial</description>
  <category>fun</category><category>recipe</category><category>clipboard</category>
  <pubDate>Sat, 03 Jul 2021 00:00:00 +0000</pubDate>
  <content:encoded><![CDATA[
<h1 id="writing_a_clipboard_manager">Writing a Clipboard Manager</h1>
<div class="admonition note"><p class="admonition-title">A word of protest</p><p>This was intended to be presented in <a href="https://conf.raku.org">The Raku Conference</a>, however the organizers insisted on using <a href="https://stallman.org/zoom.html">Zoom</a> and <a href="https://stallman.org/skype.html">Skype</a>, which are privacy invasive platforms running on proprietary software and shadily managed.</p>
</div>
<div class="franklin-toc"><ol><li>Motivation</li><li>Inspirations and Design</li><li>Daemon Implementation<ol><li>Reading Inputs</li><li>Cache Directory Setup</li><li>Comparing and Saving Selections</li><li>Command-Line Interface</li></ol></li><li>Client Implementation<ol><li>Back-End</li><li>Front-End</li></ol></li><li>Conclusion</li></ol></div>
<h2 id="motivation">Motivation</h2>
<p>Clipboard management is very important to my workflow.  To me, a clipboard manager is useful in two ways:</p>
<ol>
<li><p>It extends my &#40;rather poor&#41; temporary mundane memory by caching a few dozens of most recent selections.</p>
</li>
<li><p>It synchronizes clipboard and primary selections. Since some programs only support one kind of selection, this is particularly useful.</p>
</li>
</ol>
<p>For the first point, I have to be able to choose from the history by pressing a few keystrokes.  Having to touch the mouse during writing sessions is unacceptable.  The menu dropping down from the systray is also undesirable because I have a multi-monitor setup.  This narrows down to only one plausible option: <a href="https://launchpad.net/diodon">Diodon</a>, which I having been using on Debian for at least two years.  However, as I was migrating to NixOS earlier last month, <a href="https://github.com/NixOS/nixpkgs/pull/126190">I was unable to package it for Nix</a>.</p>
<p>Naturally, I went looking for <a href="https://search.nixos.org/packages?query&#61;clip">alternatives</a>, most of which I had tried before and did not satisfy my requirements.  <a href="https://github.com/cdown/clipmenu">clipmenu</a> got my attention however: it was made to work with dmenu&#40;-compliant launchers&#41;, which I had a rather nice experience with in <a href="https://sxmo.org">Sxmo</a> on my <a href="https://www.pine64.org/pinephone">PinePhone</a>. However, I use <a href="https://awesomewm.org">awesome</a> on my workstation and its widget toolkit covers my launcher and menu need perfectly.  I don&#39;t need <a href="https://tools.suckless.org/dmenu">dmenu</a> and do not wish to spend time configuring and theming it.  Plus, the architecture of dmenu scripts and awesome widgets vastly differs: while awesome executes the input programs, dmenu is called from the scripts.</p>
<h2 id="inspirations_and_design">Inspirations and Design</h2>
<p>As even the most plausible candidate is not a suitable replacement, I would need to write my own clipboard manager.  clipmenu is not really a good base though because it&#39;s written in shell script, something I ain&#39;t fluent in.<sup id="fnref:1">[1]</sup>  Its idea is brilliant however:</p>
<blockquote>
<ol>
<li><p><code>clipmenud</code> uses <code>clipnotify</code> to wait for new clipboard events.</p>
</li>
<li><p>If <code>clipmenud</code> detects changes to the clipboard contents, it writes them out to the cache directory and an index using a hash as the filename.</p>
</li>
</ol>
</blockquote>
<p>I later translated <a href="https://github.com/cdown/clipnotify">clipnotify</a> to <a href="https://ziglang.org">Zig</a><sup id="fnref:2">[2]</sup> and called it <a href="https://trong.loang.net/~cnx/clipbuzz">clipbuzz</a>.<sup id="fnref:3">[3]</sup> From clipbuzz&#39;s usage,</p>
<pre><code class="language-sh">while clipbuzz
do # something with xclip or xsel
done</code></pre>
<p>and this is exactly how yet another clipboard manager was written, but before we get there, let&#39;s talk about this article&#39;s sponsor&#33;</p>
<p>I&#39;m kidding d-; though we cannot jump into the implementation just yet: we only resolved the first point out of two.  How about the data structure? Hashing sounds like overengineering in this case: nobody needs more than a few dozen entries<sup id="fnref:4">[4]</sup> and hashes are not very memorable.  Printable characters can serve much better as indices.</p>
<p>What?  What happens when we run out of them?  We reuse/recycle them&#33;<sup id="fnref:5">[5]</sup> They would also fit within one single line, heck, we just store all of them in order inside a file and rotate each time there&#39;s a new selection. Picking would just be moving a char to the beginning.  The entire cache directory can just look something like this:</p>
<pre><code class="language-console">&#36; ls &#36;XDG_CACHE_HOME/&#36;project
order
R
A
K
U</code></pre>
<p>Wait, is that a sign?  We must use <a href="https://raku.org">Raku</a> to implement <code>&#36;project</code> then… Speaking of <code>&#36;project</code>, I planned to use it with awesome and <a href="https://vicious.rtfd.io">vicious</a> so let&#39;s call it something brutal, like a <em>cutting board</em>, which is <em>thớt</em> in Vietnamese, an Internet slang for <em>thread</em>.  Cool, now we have the daemon name, and conventionally the client shall be <em>threac</em>, or <em>threa client</em>.</p>
<h2 id="daemon_implementation">Daemon Implementation</h2>
<h3 id="reading_inputs">Reading Inputs</h3>
<p>Raku was chosen<sup id="fnref:6">[6]</sup> for the ease of text manipulation and seamless interfacing with external programs.  I learned it quite a while ago and has always been waiting for a chance to do something more practical with it, other than competitive programming which isn&#39;t a good fit due to Rakudo&#39;s poor performance. In Raku, the snippet from clipbuzz&#39;s README becomes:</p>
<pre><code class="language-sh">while run &#39;clipbuzz&#39; &#123;
    # do something with xclip or xsel
&#125;</code></pre>
<p>Out of all languages I know, this is by far the simplest way to <a href="https://docs.raku.org/routine/run">run</a> an external program.  Most would require one to import something or do something with the call&#39;s return value, and don&#39;t even get me start on POSIX <em>fork</em> and <em>exec</em> model.</p>
<p>OK, now what are we gonna do with <code>xclip</code>?  One obvious thing would be to read the current selection.  Raku got you covered, fam:</p>
<pre><code class="language-sh">my &#36;selection &#61; qx/xclip -out/;</code></pre>
<p>Remember when I said Raku can seamlessly interact with external programs? <a href="https://docs.raku.org/syntax/qx">qx</a> is how you capture their standard output, it is really that simple. But wait, which selection is that?  No worries, <code>xclip</code> supports both primary and clipboard:</p>
<pre><code class="language-sh">my &#36;primary &#61; qx/xclip -out -selection primary/;
my &#36;clipbroad &#61; qx/xclip -out -selection clipboard/;</code></pre>
<h3 id="cache_directory_setup">Cache Directory Setup</h3>
<p>This is when we write those selection down for later use, right? Well, we need to figure out where to save them first.  According to <a href="https://specifications.freedesktop.org/basedir-spec/latest/ar01s03.html">XDG Base Directory Specification</a>, <code>&#36;XDG_CACHE_HOME</code> shall falls back to <code>&#36;HOME/.cache</code>:</p>
<pre><code class="language-sh">my &#36;XDG_CACHE_HOME &#61; &#37;*ENV&lt;XDG_CACHE_HOME&gt; // path &#36;*HOME / &#39;.cache&#39;:;</code></pre>
<p>For convenience purposes, I defined the <code>/</code> operator as an alias for path concatination:</p>
<pre><code class="language-sh">multi sub infix:&lt;/&gt;&#40;&#36;parent, &#36;child&#41; &#123; add &#36;parent: &#36;child &#125;</code></pre>
<p>With <code>&#36;XDG_CACHE_HOME</code> defined, we can prepare the base directory as follows:</p>
<pre><code class="language-sh">my &#36;base &#61; &#36;XDG_CACHE_HOME.IO / &#39;threa&#39;;
mkdir &#36;base: unless &#36;base.e;
die &quot;thread: &#36;base: File exists&quot; when &#36;base.f;</code></pre>
<p>As <a href="https://vrurg.github.io/2021/06/16/article-on-roles">a wise man once said</a>,</p>
<blockquote>
<p>As it often happens, writing an article ends up with a bug found in Rakudo.</p>
</blockquote>
<p>In this case, there&#39;s a <a href="https://github.com/MoarVM/MoarVM/pull/1507">bug in mkdir</a> that makes it happily returns even if the target path is a file.  I&#39;m trying to fix it at the moment but <a href="https://github.com/rakudo/rakudo/pull/4408">a test</a> is still failing.  <em>Update: it passed after a maintainer bumped the dependencies to the patched version.</em></p>
<p>Anyway, back to our clipboard manager.  Here we are using <a href="https://docs.raku.org/language/control">flow controllers</a> such as <code>unless</code> and <code>when</code> in the form of <em>statement modifiers</em>, which can sometimes be easier on eyes keeping the code flat.  Existence checks like <code>e</code> &#40;exists&#41; and <code>f</code> &#40;file&#41; are also really handy.  Next, we check on the <code>order</code>:</p>
<pre><code class="language-sh">constant &#36;ALNUM &#61; &#39;ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789&#39;;sub valid&#40;&#36;path&#41; &#123;
    return False unless &#36;path.f;
    so /^\w\w&#43;&#36;/ &amp;&amp; .chars &#61;&#61; .comb.unique given trim slurp &#36;path
&#125;my &#36;order &#61; &#36;base / &#39;order&#39;;
spurt &#36;order, &#36;ALNUM unless valid &#36;order;</code></pre>
<p>Instead of printable, we only allow alphanumerics and fallback to the uppercase ones &#40;mainly because my screen can only fit as much vertically&#41;, unless <code>&#36;XDG_CACHE_HOME/threa/order</code> is a file, contains at least two unique alphanumerics &#40;exclusively&#41;.  Reading and writing files in Raku is incredibly trivial, just <code>slurp</code> and <code>spurt</code> the path.  Since we are not interested in whitespaces, they are <code>trim</code>&#39;ed from <code>order</code>.  Notice that Raku allows subroutines to be called without any parentheses—I love Lisp, but opening parenthesis <em>after</em> the function name always confuses me, especially when nested.</p>
<p>As you might have guessed, <code>given</code> is another statement modifier setting the <a href="https://docs.raku.org/syntax/&#36;_">topic variable</a> that is particularly useful in <a href="https://raku-advent.blog/2020/12/22/draft-whats-the-point-of-point-free-programming">pointfree programming</a>, where regular expressions &#40;e.g. <code>/^\w\w&#43;&#36;/</code>&#41; are matched against directly and methods are called without specifying the object.  Raku is also a weakly-typed language: <code>.comb.unique</code> &#40;a list of unique characters&#41; is coerced into an integer when compared to one &#40;number of <code>.chars</code>&#41;.</p>
<h3 id="comparing_and_saving_selections">Comparing and Saving Selections</h3>
<p>What do we do with the order then?  First we can determine the latest selection and compare it to the ones we got from <code>xclip</code> earlier to see which one is really new.  We&#39;ll also need to rotate the order, i.e. write the new selection to the <code>&#36;last</code> file and move it in front of the others that we <code>&#36;keep</code> as-is:</p>
<pre><code class="language-sh">my &#40;&#36;first, &#36;keep, &#36;last&#41; &#61; do
    given trim slurp &#36;order &#123; .comb.first, .chop, .substr: *-1 &#125;
my &#40;&#36;other, &#36;content&#41; &#61; do given try slurp &#36;base / &#36;first or &#39;&#39; &#123;
    when * ne &#36;primary &#123; &#39;clipboard&#39;, &#36;primary &#125;
    when * ne &#36;clipboard &#123; &#39;primary&#39;, &#36;clipboard &#125;
&#125;</code></pre>
<p>On the first few run, probably the cache files don&#39;t exist just yet, so we fall them back to empty ones using <code>try ... or ...</code>.  We need to know the <code>&#36;other</code> selection &#40;outdated one&#41; to later synchronize them both. In case of reselection, neither is updated and we simply skip this iteration:</p>
<pre><code class="language-sh">next unless &#36;other;</code></pre>
<p>Otherwise, let&#39;s go ahead, write down the <code>&#36;content</code>, rotate <code>&#36;order</code> and synchronize with the <code>&#36;other</code> selection:</p>
<pre><code class="language-sh">my &#36;path &#61; &#36;base / &#36;last;
spurt &#36;path, &#36;content;
spurt &#36;order, &#36;last ~ &#36;keep;
run &lt;xclip -in -selection&gt;, &#36;other, &#36;path</code></pre>
<p>That&#39;s it, now put the daemon in <code>&#36;PATH</code> and run it in <code>~/.xinitrc</code> or something IDK.  If you&#39;re worried that some selection might be too big to read that you&#39;ll the next event, asynchronize the <code>qx</code> calls by prefixing them with <code>start</code>, and <code>await</code> the results later on. It is <em>that</em> easy.</p>
<h3 id="command-line_interface">Command-Line Interface</h3>
<p>Hol up, what if I want to store the cache elsewhere or use another set of characters?  <em>&quot;Then you can go right ahead and have an intercourse with yourself, you ungrateful little piece of &#91;redacted&#93;.&quot;</em>  I would have said this were I to implement this in other languages, but luckily I got Raku, and Raku got <code>sub MAIN</code>:</p>
<pre><code class="language-sh">sub MAIN&#40;
  :&#36;children where /^\w\w&#43;&#36;/ &#61; &#36;ALNUM, #&#61; alphanumerics
  :&#36;parent &#61; &#36;XDG_CACHE_HOME           #&#61; cache path
&#41; &#123;
    my &#36;snowflakes &#61; &#36;children.comb.unique.join;
    my &#36;base &#61; &#36;parent.IO / &#39;threa&#39;;
    my &#36;order &#61; &#36;base / &#39;order&#39;;    while run &#39;clipbuzz&#39; &#123;
        ...
        spurt &#36;order, &#36;snowflakes unless valid &#36;order;
        ...
    &#125;
&#125;</code></pre>
<p>No matter how cool you think this is, it is cooler, I mean, look:</p>
<pre><code class="language-console">&#36; thread --help
Usage:
  thread &#91;--children&#91;&#61;Str where &#123; ... &#125;&#93;&#93; &#91;--parent&#61;&lt;Str&gt;&#93;
  
    --children&#91;&#61;Str where &#123; ... &#125;&#93;    alphanumerics
    --parent&#61;&lt;Str&gt;                    cache path</code></pre>
<h2 id="client_implementation">Client Implementation</h2>
<h3 id="back-end">Back-End</h3>
<p>Following the Unix™ philosophy, <code>threac</code> will do only one thing and do it well: it shall take the chosen selection and <em>schedule</em> it to move to the beginning:</p>
<pre><code class="language-sh">my &#36;XDG_CACHE_HOME &#61; &#37;*ENV&lt;XDG_CACHE_HOME&gt; // path add &#36;*HOME: &#39;.cache&#39;:;sub MAIN&#40;
   &#36;choice where /^\w?&#36;/,    #&#61; alphanumeric
  :&#36;parent &#61; &#36;XDG_CACHE_HOME #&#61; cache path
&#41; &#123;
    my &#36;base &#61; &#36;parent.IO.add: &#39;threa&#39;;
    my &#36;order &#61; add &#36;base: &#39;order&#39;;
    spurt &#36;order, S/&#36;choice&#40;.*&#41;/&#36;0&#36;choice/ with &#36;order.slurp;
    my &#36;path &#61; &#36;base.add: &#36;choice;
    run &#39;xclip&#39;, &#36;path
&#125;</code></pre>
<p>The highlight here is the non-destructive substitution <code>S///</code>, which allow regex substitution in a pointfree and pure manner. Though, instead of moving <code>&#36;choice</code> to top of the deque, we place it at the bottom and use <code>xclip</code> to trigger the daemon to do it and synchronize between selections.</p>
<h3 id="front-end">Front-End</h3>
<p>Note that <code>threac</code> does not give any output: selection history &#40;by default&#41;  are stored in a standard and convenient location to be read by any front-end of choice.  For awesome I made a menu whose each entry is wired to <code>threac</code> and <code>xdotool</code> &#40;to simulate primary paste with <code>S-Insert</code>&#41; and bind the whole thing to a keyboard shortcut.</p>
<pre><code class="language-julia">local base &#61; os.getenv&#40;&quot;HOME&quot;&#41; .. &quot;/.cache/threa/&quot;
local command &#61; &quot;threac &#37;s &amp;&amp; xdotool key shift&#43;Insert&quot;
local f &#61; io.open&#40;base .. &quot;order&quot;&#41;
local order &#61; f:read&#40;&quot;*a&quot;&#41;
f:close&#40;&#41;local items &#61; &#123;&#125;
for c in order:gmatch&#40;&quot;.&quot;&#41; do
  local f &#61; io.open&#40;base .. c&#41;
  table.insert&#40;items, &#123;f:read&#40;&quot;*a&quot;&#41;:gsub&#40;&quot;\n&quot;, &quot; &quot;&#41;, function &#40;&#41;
    awful.spawn.with_shell&#40;command:format&#40;c&#41;&#41;
  end&#125;&#41;
  f:close&#40;&#41;
end
awful.menu&#123;items &#61; items, theme &#61; &#123;width &#61; 911&#125;&#125;:show&#40;&#41;</code></pre>
<h2 id="conclusion">Conclusion</h2>
<p>Through writing the clipboard manager <a href="https://sr.ht/~cnx/threa">threa</a>, which is released under <a href="https://www.gnu.org/licenses/gpl-3.0">GNU GPLv3&#43;</a> on <a href="https://sourcehut.org">SourceHut</a>, we have discovered a few features of Raku that make it a great <em>scripting</em> language:</p>
<ul>
<li><p>Out-of-box CLI support:</p>
<ul>
<li><p>Running programs and capturing output</p>
</li>
<li><p>Environment variables</p>
</li>
<li><p>File system operations</p>
</li>
<li><p>Builtin argument parser</p>
</li>
</ul>
</li>
<li><p>Concision:</p>
<ul>
<li><p>Statement modifiers</p>
</li>
<li><p>Topic variable</p>
</li>
<li><p>First-class regex</p>
</li>
<li><p>Trivial asynchronization</p>
</li>
</ul>
</li>
</ul>
<p>As a generic programming language, Raku has other classes of characteristics that makes it useful in other larger projects such as grammars &#40;i.e. regex on steroids&#41; and OOP for human beings.  It is a truly versatile language and I really hope my words can convince someone new to try it out&#33;</p>
<table class="fndef" id="fndef:1">
    <tr>
        <td class="fndef-backref">[1]</td>
        <td class="fndef-content">I ain&#39;t proud of this, okay?</td>
    </tr>
</table><table class="fndef" id="fndef:2">
    <tr>
        <td class="fndef-backref">[2]</td>
        <td class="fndef-content">I&#39;m obsessed with exotic languages.</td>
    </tr>
</table><table class="fndef" id="fndef:3">
    <tr>
        <td class="fndef-backref">[3]</td>
        <td class="fndef-content">The <em>z</em>&#39;s are for Zig, how original, I know.</td>
    </tr>
</table><table class="fndef" id="fndef:4">
    <tr>
        <td class="fndef-backref">[4]</td>
        <td class="fndef-content">&#91;<em>citation needed</em>&#93;</td>
    </tr>
</table><table class="fndef" id="fndef:5">
    <tr>
        <td class="fndef-backref">[5]</td>
        <td class="fndef-content">Wow much environment&#33;</td>
    </tr>
</table><table class="fndef" id="fndef:6">
    <tr>
        <td class="fndef-backref">[6]</td>
        <td class="fndef-content">By some supernatural being of course&#33;</td>
    </tr>
</table>    <a href="mailto:cnx.site@loa.loang.net?In-Reply-To=%3Cblog/threa@cnx%3E&Subject=Re: Writing a Clipboard Manager">Reply via email</a>]]></content:encoded>
  <comments><![CDATA[https://lists.sr.ht/~cnx/site?search=In-Reply-To:%3Cblog/threa@cnx%3E]]></comments>
  <wfw:commentRss>https://lumvok.store/blog/threa/comments.xml</wfw:commentRss>
</item>
</channel></rss>