<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" xml:lang="fa"><generator uri="https://jekyllrb.com/" version="4.3.4">Jekyll</generator><link href="https://zmim.ir/en/feed.xml" rel="self" type="application/atom+xml" /><link href="https://zmim.ir/en/" rel="alternate" type="text/html" hreflang="fa" /><updated>2026-05-09T11:36:54+03:30</updated><id>https://zmim.ir/feed.xml</id><title type="html">محمد زینلی</title><subtitle>یادداشت‌های محمد زینلی   &lt;a href=&quot;https://github.com/piharpi&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;@github&lt;/a&gt;.</subtitle><author><name>محمد زینلی</name><email>m@zmim.ir</email></author><entry xml:lang="fa"><title type="html">جنگ نوشته</title><link href="https://zmim.ir/en/reflections-on-war/" rel="alternate" type="text/html" title="جنگ نوشته" /><published>2026-04-05T15:50:00+03:30</published><updated>2026-04-05T15:50:00+03:30</updated><id>https://zmim.ir/reflections-on-war</id><content type="html" xml:base="https://zmim.ir/reflections-on-war/"><![CDATA[<p>این یادداشت کوتاه را برای یک استوری در اینستاگرام نوشتم و به تشویق یکی از دوستان اینجا هم می‌گذارمش.</p>

<p>من به شخصه آینده روشنی در پس این جنگ برای ایران نمی‌بینم. نه چشم‌انداز روشنی برای اصلاح رفتار و ساختار حکومت می‌بینم، نه افقی روشن برای یک تغییر اساسی در نظام حکمرانی کشور. به همین دلیل، خیلی صریح و بدون واهمه از قضاوت یا کوبیده شدن توسط موافقان جنگ می‌گویم که من با مداخله نظامی به این شکل موافق نبودم.
اما حالا که این اتفاق رخ داده، پرسش اصلی برای من دیگر این نیست که چه موضعی درست‌تر بوده، بلکه این است که در دل این جنگ و در فردای آن، چه باید کرد.</p>

<p>برای من، مهم‌ترین مساله در این لحظه نه اثبات حقانیت یک موضع، بلکه تلاش برای فهمیدن پیچیدگی موقعیتی است که در آن قرار گرفته‌ایم.</p>

<p>برای خیلی‌ها دفاع از مداخله نظامی به این معنا نیست که جنگ هیچ هزینه‌ای برای مردم ندارد و حتی به این معنا نیست که حتما در صورت مداخله اوضاع بهتر می‌شود. بسیاری از کسانی که موافق این مداخله هستند، کاملا می‌پذیرند که ممکن است اوضاع حتی بدتر هم بشود. بحث بر سر این نیست که «هر چه بشود بهتر است»، بلکه دقیقا بر سر انتخاب میان چند سناریوی بد است.</p>

<p>یک سناریو، تداوم وضعیت موجود است؛ با هزینه‌های انباشته سرکوب، فرسایش اقتصادی، مهاجرت گسترده، انسداد سیاسی و اجتماعی، و روندی که در بلندمدت می‌تواند به فروپاشی‌های کنترل‌نشده منجر شود.</p>

<p>سناریوی دیگر، مداخله نظامی است؛ با همه ریسک‌های جدی آن: بی‌ثباتی، خشونت، تلفات انسانی، و حتی احتمال تکرار تجربه‌هایی شبیه عراق، سوریه یا لیبی.</p>

<p>استدلال موافقان مداخله این نیست که این ریسک‌ها وجود ندارد، بلکه این است که هزینه‌های سناریوی اول برای آن‌ها قطعی، مستمر و رو به افزایش است، در حالی که سناریوی دوم، با وجود ریسک بالا، یک امکان گسست و تغییر واقعی را باز می‌کند.</p>

<p>در واقع، برای این گروه، مساله انتخاب میان «زوال تدریجی تقریبا قطعی» و «ریسک یک جهش پرهزینه با احتمال تغییر» است.</p>

<p>ممکن است کسی این قمار را نپذیرد، و این موضع کاملا قابل احترام و قابل دفاع است. اما اگر کسی آن را بپذیرد، موضعش لزوما از جنس توهم یا بی‌توجهی به رنج مردم نیست؛ بلکه از جنس ترجیح دادن یک ریسک بزرگ به یک بن‌بست فرساینده است.</p>

<p>در عین حال، به همان اندازه باید گفت که نظر مخالفان جنگ هم کاملا قابل درک است.</p>

<p>مخالفت با جنگ به معنای دفاع از وضعیت موجود نیست. بسیاری از مخالفان، بیش از هر چیز نگران ویرانی اجتماعی، فروپاشی نهادها، گسترش خشونت و رنجی هستند که ممکن است نسل‌ها ادامه پیدا کند. تجربه تاریخی منطقه این ترس را برایشان کاملا واقعی کرده است.</p>

<p>اما شاید تصویر جامعه ایران را هم نتوان تنها در قالب این دو نگرانی توضیح داد. در کنار کسانی که تغییرات بنیادین را، با همه ریسک‌هایش، تنها راه گریز از فرسایش می‌بینند، و در کنار کسانی که از فروپاشی و بی‌ثباتی هراس دارند، بخش قابل توجهی از جامعه نیز همچنان به جمهوری اسلامی و ایده سیاسی و تاریخی پشت آن باور دارد. برای این گروه، جمهوری اسلامی صرفا یک حکومت مستقر نیست، بلکه بخشی از هویت سیاسی، دینی یا تاریخی آن‌هاست؛ چیزی که بقایش را با استقلال، ثبات یا حتی معنای ایران معاصر گره‌خورده می‌بینند. به همین دلیل، افق آینده را نه در فروپاشی کامل این نظم، بلکه در تداوم، بازسازی یا اصلاح آن جست‌وجو می‌کنند.</p>

<p>به‌نظرم بخش مهمی از تفاوت این دیدگاه‌ها، نه از ناآگاهی یا بی‌اخلاقی یک طرف، بلکه از تفاوت در تجربه زیسته و زاویه نگاه آن‌ها می‌آید.</p>

<p>کسی که سال‌ها فرسایش تدریجی زندگی، سرکوب، ناامیدی و انسداد را به‌عنوان خطر اصلی تجربه کرده، ممکن است ریسک یک گسست بزرگ را قابل تحمل‌تر ببیند.</p>

<p>در مقابل، کسی که فروپاشی اجتماعی، جنگ داخلی، آوارگی و ویرانی منطقه را خطر نزدیک‌تر می‌بیند، طبیعی است که با هر نوع مداخله نظامی مخالفت کند.</p>

<p>و در سوی دیگر، کسانی هم هستند که با وجود همه بحران‌ها، ناکارآمدی‌ها و نارضایتی‌ها، همچنان از جمهوری اسلامی دفاع می‌کنند و حاضرند برای حفظ آن هزینه بدهند؛ نه لزوما چون همه وضعیت موجود را مطلوب می‌دانند، بلکه چون فروپاشی این نظم را مساوی با فروپاشی نوعی هویت سیاسی، تاریخی یا حتی امنیت جمعی می‌بینند. نادیده گرفتن این بخش از جامعه، حتی اگر کسی با آن همدل نباشد، فهم ما از نیروهای واقعی و مسیرهای ممکن آینده ایران را ناقص می‌کند.</p>

<p>این اختلاف، بیش از آنکه نزاعی ساده میان درست و غلط باشد، اختلاف بر سر این است که هر گروه کدام نوع خطر را واقعی‌تر، فوری‌تر و تحمل‌ناپذیرتر می‌بیند.</p>

<p>اشکال اصلی جایی شروع می‌شود که هر دو طرف، موضع طرف مقابل را به یک کاریکاتور تقلیل می‌دهند.</p>

<p>اینکه نظر موافقان مداخله نظامی به «بدتر از این نمی‌شود» یا «جنگ هیچ هزینه‌ای ندارد» فروکاسته شود، همان‌قدر نادرست است که نظر مخالفان جنگ به «دفاع از جمهوری اسلامی» یا «رضایت از وضعیت موجود» تقلیل داده شود.</p>

<p>وقتی پیچیدگی و گوناگونی آدم‌های موافق و مخالف حذف می‌شود، نقد کردن موضع آن‌ها راحت‌تر می‌شود، اما دیگر نقدِ نسخه واقعی آن موضع نیست؛ نقد یک تصویر ساده‌شده و ناقص است.</p>

<p>شاید یکی از مهم‌ترین کارهایی که باید همین امروز، در دل این بحران و در اوج شکاف‌ها و خشم‌ها انجام شود، ساختن یک گفتار مشترک برای باهم‌زیستن باشد.</p>

<p>گفتاری که از ضرورت تاریخی برپایی دوباره ایران بر محور ایده باهم‌زیستن دفاع کند؛
اینکه با وجود همه اختلاف‌های عمیق، خشم‌ها، زخم‌ها و تجربه‌های متفاوت، آینده‌ای برای این سرزمین بدون پذیرش تکثر دیدگاه‌ها و بدون امکان زندگی در کنار یکدیگر ساخته نمی‌شود.</p>

<p>اگر قرار است از این وضعیت عبور کنیم، این عبور فقط سیاسی نیست؛
باید دوباره یاد بگیریم که همدیگر را بفهمیم، پیش از آنکه یکدیگر را حذف کنیم.</p>

<p>شاید مهم‌ترین وظیفه امروز ما همین باشد:
حفظ امکان باهم‌ماندن، حتی در اوج اختلاف.</p>

<p>چون ایرانِ پس از جنگ، هر شکلی که به خود بگیرد، ناگزیر خانه مشترک همه ما خواهد بود؛
و اگر زبان گفت‌وگو و امکان باهم‌زیستن را از همین امروز نسازیم، هیچ تغییر سیاسی به‌تنهایی آینده‌ای روشن برای این سرزمین نخواهد ساخت.</p>

<p>آینده ایران نه فقط در میدان سیاست، که در توان ما برای باهم‌ماندن، باهم‌شنیدن و باهم‌ساختن رقم می‌خورد.</p>]]></content><author><name>محمد زینلی</name><email>m@zmim.ir</email></author><summary type="html"><![CDATA[یادداشتی کوتاه درباره جنگ]]></summary></entry><entry xml:lang="en"><title type="html">Why Do Messages from Inside Iran Reach Outside More Easily?</title><link href="https://zmim.ir/en/inbound-outbound/" rel="alternate" type="text/html" title="Why Do Messages from Inside Iran Reach Outside More Easily?" /><published>2026-01-24T12:10:00+03:30</published><updated>2026-01-24T12:10:00+03:30</updated><id>https://zmim.ir/inbound-outbound</id><content type="html" xml:base="https://zmim.ir/inbound-outbound/"><![CDATA[<p>Unfortunately, Iran’s internet connection has been cut for about two weeks and distressing news has left all of us in a bad state. Free access to the internet is a fundamental and basic right for all people. Here’s hoping for better days.</p>

<p>Here I want to explain why, after a brief and limited restoration of connectivity, messages sent from inside Iran are more easily received outside the country, while the reverse path — messages sent from outside Iran — typically have more difficulty reaching their destination.</p>

<h1 id="asymmetric-routing">Asymmetric Routing</h1>

<p>During a crisis or period of restrictions, internet routes typically become asymmetric. This means the outbound path from Iran (sending data to the outside) is less restricted, while the inbound path to Iran (receiving data from outside) is more heavily filtered or blocked. The reason is that controlling incoming information is more important to surveillance systems.</p>

<p>Simply put, “sending” becomes easier than “receiving.” The result of this asymmetry is that a request leaves from inside the country, but the response from outside may never return.</p>

<p>Internet routes become asymmetric (Asymmetric Routing) during outages. The outbound path from Iran is usually more open while the inbound path is more restricted. On the other hand, VPNs and proxies have a harder time re-establishing inbound connections on an unstable internet, especially if they use UDP or have weak keep-alive mechanisms. Also, DNS and external servers sometimes send responses via a route whose return path to Iran is blocked.</p>

<h1 id="stateless-firewall">Stateless Firewall</h1>

<p>A stateless firewall checks each internet packet completely independently. It doesn’t care what was exchanged before this packet. It only looks at whether this packet matches the defined rules.</p>

<h2 id="advantages">Advantages</h2>

<p>Simplicity is the most important advantage of this type of firewall. It is easy to implement, consumes few resources, and has high processing speed. For this reason, it is suitable for simple routers or networks with very high traffic volumes.</p>

<h2 id="disadvantages">Disadvantages</h2>

<p>The main problem with this firewall is that it has no “context.” It cannot understand what connection a packet is part of. For this reason, its rules must either be very permissive (which reduces security) or very strict (which causes legitimate connections to be dropped), and it is not well-suited for precise control of inbound traffic.</p>

<h1 id="stateful-firewall">Stateful Firewall</h1>

<p>A stateful firewall, unlike a stateless one, has memory. When a connection is initiated from inside the network, the firewall records it in a table called the state table. After that, only packets that are a continuation of that recorded connection are allowed through.</p>

<p>If a packet arrives from outside for which no prior connection exists, the firewall treats it as suspicious and typically blocks it.</p>

<h2 id="advantages-1">Advantages</h2>

<p>Higher security is the main advantage of a stateful firewall, because it only allows the continuation of connections that started “legitimately.” Control of inbound traffic is much more precise and the risk of abuse is reduced.</p>

<h2 id="disadvantages-1">Disadvantages</h2>

<p>This type of firewall is more complex and consumes more resources. Additionally, it is sensitive to network instability. If a connection is interrupted and reconnects, or a packet is lost, the state may be cleared and subsequent packets may be incorrectly blocked.</p>

<h1 id="summary-comparison-of-stateless-and-stateful">Summary Comparison of Stateless and Stateful</h1>

<p>In short, stateless is simple and fast but has no awareness of connections. Stateful is smarter and more secure but can drop legitimate connections on an unstable internet. Iran’s internet restrictions more likely use stateful firewalls, because controlling inbound traffic is their priority.</p>

<p>In Iran’s internet restrictions, stateful firewalls are probably used more — meaning if a connection is initiated from inside the country, the response from outside comes back more easily, but if someone tries to send a message from outside without a prior connection from inside, the firewall typically blocks it.</p>

<p>That is why messages sent from inside to outside have a better chance of arriving than messages sent from outside to inside.</p>

<h1 id="tcp-and-its-key-messages">TCP and Its Key Messages</h1>

<p>Most internet communications (web, messaging apps, email) run on a protocol called TCP. Before sending data, TCP has a specific process for establishing a connection.</p>

<p>First, a packet called SYN is sent, meaning “I want to establish a connection.” The other party responds with SYN-ACK, meaning “I received your request and I’m ready.” The sender then confirms with ACK and the connection is established. After that, the actual DATA is exchanged. Finally, one of the two parties announces with FIN that the connection is over and it is closed.</p>

<p>This defined structure allows stateful firewalls to determine which connections are legitimate.</p>

<div style="text-align: center;">
    <img src="/inbound-outbound/udp-tcp.jpg" style="max-width: 80%; margin: 10px;" alt="Comparison of UDP and TCP" />
</div>

<h1 id="udp-and-the-concept-of-pseudo-state">UDP and the Concept of Pseudo-State</h1>

<p>UDP, unlike TCP, has none of these steps. It has no defined start, no acknowledgment, no end. Each UDP packet is sent independently, without the other party necessarily sending a response.</p>

<p>To avoid completely blocking UDP, stateful firewalls create something called a pseudo-state. This means if a UDP packet is sent from inside the network to a specific destination, the firewall temporarily allows a response from that same destination to return. If this time window expires or the internet is unstable, subsequent packets arriving from outside are no longer recognized as legitimate and are dropped.</p>

<p>For this reason, UDP-based connections — such as some VPNs, voice and video calls — become unstable very quickly when internet connectivity is intermittent.</p>

<h1 id="summary">Summary</h1>

<p>The fact that messages from inside Iran reach outside more easily but not vice versa is the result of a combination of factors: the asymmetric nature of internet routes, the widespread use of stateful firewalls, the sensitivity of these firewalls to network instability, and the inherent differences between TCP and UDP. This behavior is neither a bug nor accidental — it is the product of design and policy decisions at the network infrastructure level.</p>]]></content><author><name>محمد زینلی</name><email>m@zmim.ir</email></author><category term="network" /><category term="internet" /><category term="freedom" /><summary type="html"><![CDATA[During internet outages or severe restrictions in Iran, many people experience that messages sent from inside the country usually reach outside more easily, while messages sent from outside to inside either arrive late or not at all. This behavior is not accidental or random — it is rooted in the design of the network, the type of filtering, and differences in internet protocols.]]></summary></entry><entry xml:lang="en"><title type="html">Managing Dotfiles with a Git Bare Repository</title><link href="https://zmim.ir/en/dotfiles-git/" rel="alternate" type="text/html" title="Managing Dotfiles with a Git Bare Repository" /><published>2023-07-01T21:10:00+03:30</published><updated>2023-07-01T21:10:00+03:30</updated><id>https://zmim.ir/dotfiles-git</id><content type="html" xml:base="https://zmim.ir/dotfiles-git/"><![CDATA[<h1 id="dotfiles">Dotfiles</h1>

<p>One of the greatest advantages of GNU/Linux is its customizability. Many users personalize their system by modifying configuration files.
As we use our system over time, these files change continuously, and when their number grows and you use them across different systems, managing them becomes more difficult.
One of the best methods for managing these files is using <code class="language-plaintext highlighter-rouge">Git</code>.</p>

<h1 id="window-managers-and-their-greatest-advantage-customizability">Window Managers and Their Greatest Advantage: Customizability</h1>

<p>I have been using GNU/Linux on my personal systems for a long time and have preferred minimal window managers over large desktop environments like GNOME and KDE for years.
Using window managers has made my system lighter and fully customized for my use.</p>

<p>When you use window managers instead of large desktops, you have to configure many system settings and tools yourself.</p>

<p>This makes you better understand the system you use and allows you to change it according to your needs.</p>

<p>Managing configuration files for use across different systems with different hardware and operating systems may seem difficult. Using a <code class="language-plaintext highlighter-rouge">Git bare repository</code> is one of the best and most elegant solutions for this. Since most of us already use <code class="language-plaintext highlighter-rouge">Git</code> to manage our projects, this approach requires no new tool installation or learning.</p>

<h1 id="other-tools">Other Tools</h1>

<p>Many tools exist for managing dotfiles. Perhaps the most well-known is <a href="https://www.gnu.org/software/stow/" target="_blank" rel="noopener noreferrer"><code class="language-plaintext highlighter-rouge">GNU Stow</code></a>. A list of these tools can be found <a href="https://dotfiles.github.io/utilities" target="_blank" rel="noopener noreferrer">here</a> or on the <a href="https://wiki.archlinux.org/title/Dotfiles#Tools" target="_blank" rel="noopener noreferrer">Arch Wiki</a>.</p>

<h1 id="the-git-bare-repo-method">The <code class="language-plaintext highlighter-rouge">Git bare repo</code> Method</h1>

<p>The first place I found this very clever method was in <a href="https://news.ycombinator.com/item?id=11070797">this Hacker News discussion</a>. After that I read about it in various places and decided to use this method for managing my dotfiles.</p>

<h2 id="advantages">Advantages</h2>

<ul>
  <li>No need to install extra tools</li>
  <li>No symlinks required</li>
  <li>Files are managed in a <code class="language-plaintext highlighter-rouge">Git</code> repository</li>
  <li>You can use different branches for different systems</li>
  <li>You can easily replicate your configuration when setting up a new system</li>
</ul>

<h2 id="how-does-it-work">How Does It Work?</h2>

<p>In this method, the <code class="language-plaintext highlighter-rouge">Git bare</code> repository is stored in a side folder (such as <code class="language-plaintext highlighter-rouge">$HOME/.cfg</code> or <code class="language-plaintext highlighter-rouge">$HOME/.myconfig</code>). Using a special <code class="language-plaintext highlighter-rouge">alias</code>, <code class="language-plaintext highlighter-rouge">Git</code> commands are run against this side folder and won’t interfere with dotfiles or other <code class="language-plaintext highlighter-rouge">Git</code> repositories.</p>

<h2 id="starting-from-scratch">Starting from Scratch</h2>

<p>If you haven’t previously tracked your configurations in a <code class="language-plaintext highlighter-rouge">Git</code> repository, you can easily start using this method with these commands:</p>

<div class="code-block">

<figure class="highlight"><pre><code class="language-bash" data-lang="bash">git init <span class="nt">--bare</span> <span class="nv">$HOME</span>/.cfg
<span class="nb">alias </span><span class="nv">config</span><span class="o">=</span><span class="s1">'/usr/bin/git --git-dir=$HOME/.cfg/ --work-tree=$HOME'</span>
config config <span class="nt">--local</span> status.showUntrackedFiles no
<span class="nb">echo</span> <span class="s2">"alias config='/usr/bin/git --git-dir=</span><span class="nv">$HOME</span><span class="s2">/.cfg/ --work-tree=</span><span class="nv">$HOME</span><span class="s2">'"</span> <span class="o">&gt;&gt;</span> <span class="nv">$HOME</span>/.config/shell/.aliasrc</code></pre></figure>

</div>

<p>The first command creates a folder at <code class="language-plaintext highlighter-rouge">~/.cfg</code> which is a <a href="http://www.saintsjd.com/2011/01/what-is-a-bare-git-repository/"><code class="language-plaintext highlighter-rouge">Git bare</code></a> repository that tracks our files.</p>

<p>Then we create an <code class="language-plaintext highlighter-rouge">alias</code> so that whenever we want to interact with the dotfiles repository, we use it instead of regular <code class="language-plaintext highlighter-rouge">git</code>.</p>

<p>We set a local flag to hide files we are not yet tracking. This way, when you type <code class="language-plaintext highlighter-rouge">config status</code> and other commands, files you don’t want to track won’t show up as untracked.
I prefer to use <code class="language-plaintext highlighter-rouge">echo "*" &gt; ~/.gitignore</code> instead. With this approach, you must use the <code class="language-plaintext highlighter-rouge">-f</code> flag to add files in <code class="language-plaintext highlighter-rouge">config</code> commands, e.g.: <code class="language-plaintext highlighter-rouge">config add -f .dotfile</code>. This helps prevent accidentally adding and committing extra files with <code class="language-plaintext highlighter-rouge">config add .</code>.</p>

<p>I use <code class="language-plaintext highlighter-rouge">zsh</code> and have sourced the <code class="language-plaintext highlighter-rouge">~/.config/shell/aliasrc</code> file in <code class="language-plaintext highlighter-rouge">.zshrc</code>. Note that if you use a different shell, add the <code class="language-plaintext highlighter-rouge">alias</code> for that shell in the fourth line:</p>

<div class="code-block">

<figure class="highlight"><pre><code class="language-bash" data-lang="bash"><span class="c"># for bash</span>
<span class="nb">echo</span> <span class="s2">"alias config='/usr/bin/git --git-dir=</span><span class="nv">$HOME</span><span class="s2">/.cfg/ --work-tree=</span><span class="nv">$HOME</span><span class="s2">'"</span> <span class="o">&gt;&gt;</span> <span class="nv">$HOME</span>/.bashrc

<span class="c"># for zsh</span>
<span class="nb">echo</span> <span class="s2">"alias config='/usr/bin/git --git-dir=</span><span class="nv">$HOME</span><span class="s2">/.cfg/ --work-tree=</span><span class="nv">$HOME</span><span class="s2">'"</span> <span class="o">&gt;&gt;</span> <span class="nv">$HOME</span>/.config/zsh/.zshrc</code></pre></figure>

</div>

<p>For <code class="language-plaintext highlighter-rouge">fish</code>, you can use this function:</p>

<div class="code-block">

<figure class="highlight"><pre><code class="language-bash" data-lang="bash"><span class="k">function </span>config <span class="nt">-w</span> git <span class="nt">-d</span> <span class="s2">"Manages dotfiles"</span>
    git <span class="nt">--git-dir</span><span class="o">=</span><span class="nv">$HOME</span>/.dot <span class="nt">--work-tree</span><span class="o">=</span><span class="nv">$HOME</span> <span class="nv">$argv</span>
end</code></pre></figure>

</div>

<h2 id="installing-files-on-a-new-system-or-migrating-existing-files">Installing Files on a New System or Migrating Existing Files</h2>

<p>If you have previously stored your configurations in a <code class="language-plaintext highlighter-rouge">Git</code> repository, on a new system you can migrate to this method with the following steps:</p>

<p>Before installing, make sure you’ve added the <code class="language-plaintext highlighter-rouge">alias</code> to <code class="language-plaintext highlighter-rouge">.bashrc</code> or <code class="language-plaintext highlighter-rouge">.zshrc</code> in your repository.</p>

<div class="code-block">

<figure class="highlight"><pre><code class="language-bash" data-lang="bash"><span class="nb">echo</span> <span class="s2">"alias config='/usr/bin/git --git-dir=</span><span class="nv">$HOME</span><span class="s2">/.cfg/ --work-tree=</span><span class="nv">$HOME</span><span class="s2">'"</span> <span class="o">&gt;&gt;</span> <span class="nv">$HOME</span>/.config/shell/.aliasrc</code></pre></figure>

</div>

<p>And that your source repository ignores the folder you’re using as <code class="language-plaintext highlighter-rouge">git bare</code> to avoid strange problems: <code class="language-plaintext highlighter-rouge">echo ".cfg" &gt;&gt; ~/.gitignore</code> or as mentioned above: <code class="language-plaintext highlighter-rouge">echo "*" &gt;&gt; ~/.gitignore</code></p>

<p>Now clone your configuration repository as a <code class="language-plaintext highlighter-rouge">bare</code> repository into a hidden folder at <code class="language-plaintext highlighter-rouge">$HOME/.cfg</code>:</p>

<div class="code-block">

<figure class="highlight"><pre><code class="language-bash" data-lang="bash">git clone <span class="nt">--bare</span> &lt;git-repo-url&gt; <span class="nv">$HOME</span>/.cfg</code></pre></figure>

</div>

<p>Define the <code class="language-plaintext highlighter-rouge">alias</code> in your current shell so you can use it:</p>

<div class="code-block">

<figure class="highlight"><pre><code class="language-bash" data-lang="bash"><span class="nb">alias </span><span class="nv">config</span><span class="o">=</span><span class="s1">'/usr/bin/git --git-dir=$HOME/.cfg/ --work-tree=$HOME'</span></code></pre></figure>

</div>

<p>Check out the actual files using <code class="language-plaintext highlighter-rouge">config checkout</code>. I use the <code class="language-plaintext highlighter-rouge">home</code> branch in <a href="https://github.com/mhdzli/dotfiles/tree/home" target="_blank" rel="noopener noreferrer">my repository</a> to store these files, so for me the command would be: <code class="language-plaintext highlighter-rouge">config checkout home</code>. If you use different branches for different systems, make sure you check out the right files.</p>

<p>If there are already similar configuration files in your home directory, the above command will fail with an error.
You’ll need to delete or move those files. You can list all files in the repository with this command:</p>

<div class="code-block">

<figure class="highlight"><pre><code class="language-bash" data-lang="bash">config ls-tree <span class="nt">--full-tree</span> <span class="nt">--name-only</span> <span class="nt">-r</span> &lt;YOUR BRANCH&gt;

<span class="c">#for me</span>
config ls-tree <span class="nt">--full-tree</span> <span class="nt">--name-only</span> <span class="nt">-r</span> home</code></pre></figure>

</div>

<p>Then configure this setting to hide untracked files: <code class="language-plaintext highlighter-rouge">config config --local status.showUntrackedFiles no</code></p>

<p>Congratulations! From now on you can manage your files like this:</p>

<div class="code-block">

<figure class="highlight"><pre><code class="language-bash" data-lang="bash">config status
config add .vimrc
config commit <span class="nt">-m</span> <span class="s2">"Add vimrc"</span>
config add .bashrc
config commit <span class="nt">-m</span> <span class="s2">"Add bashrc"</span>
config push</code></pre></figure>

</div>

<p>If you used <code class="language-plaintext highlighter-rouge">*</code> in <code class="language-plaintext highlighter-rouge">.gitignore</code> instead of the <code class="language-plaintext highlighter-rouge">status.showUntrackedFiles</code> setting, the commands will look like this:</p>

<div class="code-block">

<figure class="highlight"><pre><code class="language-bash" data-lang="bash">config status
config add <span class="nt">-f</span> .vimrc
config commit <span class="nt">-m</span> <span class="s2">"Add vimrc"</span>
config add <span class="nt">-f</span> .bashrc
config commit <span class="nt">-m</span> <span class="s2">"Add bashrc"</span>
config push</code></pre></figure>

</div>

<h1 id="resources">Resources:</h1>

<ul>
  <li><a href="https://news.ycombinator.com/item?id=11070797" target="_blank" rel="noopener noreferrer">Hacker News</a></li>
  <li><a href="https://www.atlassian.com/git/tutorials/dotfiles" target="_blank" rel="noopener noreferrer">Atlassian’s Blog</a></li>
  <li><a href="https://coffeeaddict.dev/how-to-manage-dotfiles-with-git-bare-repo" target="_blank" rel="noopener noreferrer">Coffee Addict</a></li>
</ul>

<h1 id="my-dotfiles"><a href="https://github.com/mhdzli/dotfiles" target="_blank" rel="noopener noreferrer">My Dotfiles</a></h1>]]></content><author><name>محمد زینلی</name><email>m@zmim.ir</email></author><category term="git" /><category term="dotfiles" /><category term="linux" /><summary type="html"><![CDATA[One of the greatest advantages of GNU/Linux is its customizability. Many users personalize their system by modifying configuration files. One of the best methods for managing these files is using Git.]]></summary></entry><entry xml:lang="en"><title type="html">Keyboard Configuration in GNU/Linux</title><link href="https://zmim.ir/en/keyboard-configurations/" rel="alternate" type="text/html" title="Keyboard Configuration in GNU/Linux" /><published>2022-04-22T10:42:36+04:30</published><updated>2022-04-22T10:42:36+04:30</updated><id>https://zmim.ir/keyboard-configurations</id><content type="html" xml:base="https://zmim.ir/keyboard-configurations/"><![CDATA[<p>Using two languages simultaneously is a necessity for Persian-speaking computer users. Persian language support in GNU/Linux has been available for a long time and has consistently maintained better standards than Windows. Still, further personalization always leads to a more pleasant computing experience. Using two languages simultaneously, combined with the lack of a single standard for Persian typing, causes Persian-speaking users to occasionally encounter problems that English-speaking users never face. A while ago, a friend was looking for a way to add a specific character to their keyboard. Their inability to find a suitable guide prompted me to write about this topic.</p>

<p>Here I try to write about keyboard configuration in the <code class="language-plaintext highlighter-rouge">tty</code> console environment and graphical environments <code class="language-plaintext highlighter-rouge">X11</code> and the <code class="language-plaintext highlighter-rouge">Sway</code> window manager on Wayland. Keyboard configuration on Wayland depends on the compositor you’re using, but most of what is discussed here can also be applied on other Wayland compositors, especially those based on <code class="language-plaintext highlighter-rouge">wlroots</code>.</p>

<h1 id="different-keyboards-and-layouts">Different Keyboards and Layouts</h1>

<p>Most of us use the English and Persian <code class="language-plaintext highlighter-rouge">QWERTY</code> layout on our keyboards, an example of which can be seen in the image below.</p>

<div style="text-align: center;">
    <img src="/keyboard-configurations/600px-KB_United_States.png" style="max-width: 80%; margin: 10px;" alt="English QWERTY keyboard" />
</div>

<p>But there are other layouts for <code class="language-plaintext highlighter-rouge">QWERTY</code> keyboards depending on the language. For example, the French <code class="language-plaintext highlighter-rouge">AZERTY</code> keyboard, which makes changes to the English <code class="language-plaintext highlighter-rouge">QWERTY</code> layout for better compatibility with the French language:</p>

<div style="text-align: center;">
    <img src="/keyboard-configurations/KB_France.png" style="max-width: 80%; margin: 10px;" alt="French AZERTY keyboard" />
</div>

<p>For more information, you can read the relevant page on <a href="https://en.wikipedia.org/wiki/Keyboard_layout" target="_blank" rel="noopener noreferrer">Wikipedia</a>.</p>

<p>There are also other English keyboard layouts, of which <a href="https://en.wikipedia.org/wiki/Dvorak_keyboard_layout" target="_blank" rel="noopener noreferrer"><code class="language-plaintext highlighter-rouge">DVORAK</code></a> and <a href="https://en.wikipedia.org/wiki/Colemak" target="_blank" rel="noopener noreferrer"><code class="language-plaintext highlighter-rouge">COLEMAK</code></a> are the most well-known.</p>

<p>Example of the <code class="language-plaintext highlighter-rouge">DVORAK</code> keyboard:</p>

<div style="text-align: center;">
    <img src="/keyboard-configurations/KB_United_States_Dvorak.png" style="max-width: 80%; margin: 10px;" alt="DVORAK keyboard" />
</div>

<p>Example of the <code class="language-plaintext highlighter-rouge">COLEMAK</code> keyboard:</p>

<div style="text-align: center;">
    <img src="/keyboard-configurations/600px-KB_US-Colemak.png" style="max-width: 80%; margin: 10px;" alt="COLEMAK keyboard" />
</div>

<p>The <a href="https://kbdlayout.info/kbdfar" target="_blank" rel="noopener noreferrer">standard Persian keyboard</a> is also classified as a <code class="language-plaintext highlighter-rouge">QWERTY</code> keyboard, with the following key layout:</p>

<div style="text-align: center;">
    <img src="/keyboard-configurations/KB_Persian.png" style="max-width: 80%; margin: 10px;" alt="Standard Persian keyboard" />
</div>

<h1 id="keyboard-configuration-in-tty">Keyboard Configuration in <code class="language-plaintext highlighter-rouge">tty</code></h1>

<p>You may rarely need to change keyboard settings in the console (tty) environment. Most of us use the default English <code class="language-plaintext highlighter-rouge">QWERTY</code> layout. However, as mentioned, other layouts exist for English as well. This is one reason you might want to use a different layout in the console.</p>

<p>If you use <code class="language-plaintext highlighter-rouge">Systemd</code> as your init system, you can see a list of available layouts with <code class="language-plaintext highlighter-rouge">localectl list-keymaps</code>. For me, this command shows 232 different keyboards.</p>

<div class="code-block">

<figure class="highlight"><pre><code class="language-bash" data-lang="bash"><span class="nv">$ </span>localectl list-keymaps | <span class="nb">wc</span> <span class="nt">-l</span>
232</code></pre></figure>

</div>

<p>You can also search for a specific keyboard using <code class="language-plaintext highlighter-rouge">grep</code>:</p>

<div style="text-align: center;">
    <img src="/keyboard-configurations/localectl.png" style="max-width: 80%; margin: 10px;" alt="localectl command" />
</div>

<p>If you don’t use <code class="language-plaintext highlighter-rouge">Systemd</code>, the <code class="language-plaintext highlighter-rouge">find</code> command can give you a list of available keyboards. The output for me looks like this:</p>

<div class="code-block">

<figure class="highlight"><pre><code class="language-bash" data-lang="bash"><span class="nv">$ </span>find /usr/share/kbd/keymaps/ <span class="nt">-type</span> f <span class="nt">-name</span> <span class="s2">"*"</span>
/usr/share/kbd/keymaps/amiga/amiga-de.map.gz
/usr/share/kbd/keymaps/amiga/amiga-us.map.gz
...</code></pre></figure>

</div>

<p>To find a specific keyboard, replace <code class="language-plaintext highlighter-rouge">*</code> with the search term surrounded by <code class="language-plaintext highlighter-rouge">*</code> characters:</p>

<div class="code-block">

<figure class="highlight"><pre><code class="language-bash" data-lang="bash"><span class="nv">$ </span>find /usr/share/kbd/keymaps/ <span class="nt">-type</span> f <span class="nt">-name</span> <span class="s2">"*uk*"</span>
/usr/share/kbd/keymaps/atari/atari-uk-falcon.map.gz
/usr/share/kbd/keymaps/i386/dvorak/dvorak-uk.map.gz
/usr/share/kbd/keymaps/i386/dvorak/dvorak-ukp.map.gz
/usr/share/kbd/keymaps/i386/qwerty/uk.map.gz
/usr/share/kbd/keymaps/mac/all/mac-uk.map.gz
/usr/share/kbd/keymaps/sun/sunt5-uk.map.gz
/usr/share/kbd/keymaps/sun/sunt6-uk.map.gz</code></pre></figure>

</div>

<p>To set your desired keyboard, use the <code class="language-plaintext highlighter-rouge">loadkeys</code> command. Note that running this command requires root access:</p>

<div class="code-block">

<figure class="highlight"><pre><code class="language-bash" data-lang="bash"><span class="nv">$ </span><span class="nb">sudo </span>loadkeys fr
<span class="nv">$ </span><span class="nb">sudo </span>loadkeys i386/azerty/fr.map.gz</code></pre></figure>

</div>

<p>You can use either the keyboard name or the full path to the <code class="language-plaintext highlighter-rouge">keymap</code> file. The keyboard you choose must be suitable for the console and its characters must be supported. If you try to set the Persian keyboard for the console, you will encounter an error:</p>

<div class="code-block">

<figure class="highlight"><pre><code class="language-bash" data-lang="bash"><span class="nv">$ </span><span class="nb">sudo </span>loadkeys fa
unicode keysym out of range: U+FDFC
syntax error, unexpected ERROR, expecting NUMBER or LITERAL or PLUS or UNUMBER</code></pre></figure>

</div>

<p>To make these settings permanent, you can use the <code class="language-plaintext highlighter-rouge">KEYMAP</code> keyword in the <code class="language-plaintext highlighter-rouge">/etc/vconsole.conf</code> file.</p>

<div class="code-block">

<figure class="highlight"><pre><code class="language-bash" data-lang="bash"><span class="nv">$ </span><span class="nb">cat</span> /etc/vconsole.conf
<span class="nv">FONT</span><span class="o">=</span>ter-224b
<span class="nv">KEYMAP</span><span class="o">=</span>us</code></pre></figure>

</div>

<h1 id="keyboard-configuration-in-x11">Keyboard Configuration in <code class="language-plaintext highlighter-rouge">X11</code></h1>

<h2 id="setxkbmap"><code class="language-plaintext highlighter-rouge">setxkbmap</code></h2>

<p>For configuring the keyboard in graphical environments that use <code class="language-plaintext highlighter-rouge">X11</code> server, each environment like GNOME or KDE has its own dedicated program. A more general and simpler way is to use the <code class="language-plaintext highlighter-rouge">setxkbmap</code> command.</p>

<p>I use this command as follows:</p>

<div class="code-block">

<figure class="highlight"><pre><code class="language-bash" data-lang="bash"><span class="nv">$ </span>setxkbmap <span class="nt">-model</span> thinkpad us,ir <span class="nt">-option</span> <span class="s2">"grp:shifts_toggle,caps:escape_shifted_capslock,altwin:prtsc_rwin,lv3:ralt_switch"</span></code></pre></figure>

</div>

<p>Keyboard configuration with <code class="language-plaintext highlighter-rouge">setxkbmap</code> has three parts:</p>

<ul>
  <li>Keyboard model: you can specify your keyboard model with <code class="language-plaintext highlighter-rouge">-model &lt;YOUR-KEYBOARD-MODEL&gt;</code>.</li>
  <li>layout[s]: you can configure any number of available layouts for use with the keyboard. Note that you must use <code class="language-plaintext highlighter-rouge">,</code> to separate their names.</li>
  <li>Special options: you can specify special keyboard options with <code class="language-plaintext highlighter-rouge">-options &lt;YOUR-KEYBOARD-OPTIONS&gt;</code>.</li>
</ul>

<p>In the command I use, I’ve set the keyboard model to <code class="language-plaintext highlighter-rouge">thinkpad</code> and configured both English and Persian.</p>

<p>In the special options section, using <code class="language-plaintext highlighter-rouge">grp:shifts_toggle</code> I’ve specified that switching between keyboard languages is done by pressing both <code class="language-plaintext highlighter-rouge">Shift</code> keys simultaneously.</p>

<p>It may seem unusual, but for me — since my hands are almost always on the keyboard — it’s more convenient than using <code class="language-plaintext highlighter-rouge">Alt+Shift</code>. I also have other keys for switching keyboard language so I can switch with one hand if needed.</p>

<p>With <code class="language-plaintext highlighter-rouge">caps:escape_shifted_capslock</code> I’ve specified that the <code class="language-plaintext highlighter-rouge">CapsLock</code> key acts as the <code class="language-plaintext highlighter-rouge">Escape</code> key.</p>

<p>I use the Vim editor for editing text files and frequently use the <code class="language-plaintext highlighter-rouge">Escape</code> key to exit various modes. The standard location of this key at the top-left corner of the keyboard is far from reach. Mapping <code class="language-plaintext highlighter-rouge">CapsLock</code> as <code class="language-plaintext highlighter-rouge">Escape</code> makes it much easier for me to use.</p>

<p>The next option is <code class="language-plaintext highlighter-rouge">altwin:prtsc_rwin</code> which specifies that the <code class="language-plaintext highlighter-rouge">PrintScreen (PrtSc)</code> key — located next to the right <code class="language-plaintext highlighter-rouge">Alt</code> key on my laptop keyboard — acts as a second <code class="language-plaintext highlighter-rouge">Meta</code> or <code class="language-plaintext highlighter-rouge">Mod</code> key (the <code class="language-plaintext highlighter-rouge">Win</code> logo key).</p>

<p>For years I have not used desktop environments on my computer, preferring window managers instead, which are lighter and more customizable. Most of my keyboard shortcuts are in the form <code class="language-plaintext highlighter-rouge">Mod+&lt;SOME-KEY&gt;</code>, and there are many of them. Having the <code class="language-plaintext highlighter-rouge">Mod</code> key on both sides of the keyboard makes using these shortcuts more comfortable.</p>

<p>Finally, <code class="language-plaintext highlighter-rouge">lv3:ralt_switch</code> specifies that layer 3 of the keyboard can be accessed using the right <code class="language-plaintext highlighter-rouge">Alt</code> key. As you know, each language on the keyboard has more than one layer of characters. In almost all cases, you can type layer 2 characters using the <code class="language-plaintext highlighter-rouge">Shift</code> key. For English these are uppercase letters and some other commonly used characters. For Persian on the standard keyboard, <code class="language-plaintext highlighter-rouge">ژ</code> is on the second layer of the <code class="language-plaintext highlighter-rouge">ز</code> key. Some other common characters are also on this layer, including <code class="language-plaintext highlighter-rouge">ZWNJ</code> (zero-width non-joiner). Interestingly, some less-used characters are on layer 3 and below. <code class="language-plaintext highlighter-rouge">NBSP</code> (non-breaking space) is one of them, located on layer 3 of the space key. I use the right <code class="language-plaintext highlighter-rouge">Alt</code> key to access this layer.</p>

<p>An important question is where to find a list of models, layouts, and special options. Simply open the documentation with <code class="language-plaintext highlighter-rouge">man xkeyboard-config</code> in your terminal and read it. You’ll find a comprehensive list of all options with clear explanations for each. Searching <code class="language-plaintext highlighter-rouge">man xkeyboard-config &lt;YOUR-DISTRIBUTION&gt;</code> in your browser will also find the documentation for your distribution. For example, for <a href="https://man.archlinux.org/man/xkeyboard-config.7.en" target="_blank" rel="noopener noreferrer"><code class="language-plaintext highlighter-rouge">Arch Linux</code></a>.</p>

<p>Of course, to find layouts you can also look in <code class="language-plaintext highlighter-rouge">/usr/share/X11/xkb/symbols</code>.</p>

<div class="code-block">

<figure class="highlight"><pre><code class="language-bash" data-lang="bash"><span class="nv">$ </span><span class="nb">ls</span> /usr/share/X11/xkb/symbols</code></pre></figure>

</div>

<p>The next question is how to make these settings permanent. I use the <code class="language-plaintext highlighter-rouge">xorg-xinit</code> package to start the <code class="language-plaintext highlighter-rouge">X11</code> graphical environment and run the <code class="language-plaintext highlighter-rouge">startx</code> command directly from the console without using a Display Manager. When this command is run, it reads graphical environment settings from text files. One of these files is <code class="language-plaintext highlighter-rouge">.xinitrc</code> in the <code class="language-plaintext highlighter-rouge">$HOME</code> directory. I simply put any command I need to set up my graphical environment in a file and call it from inside <code class="language-plaintext highlighter-rouge">.xinitrc</code>. I’ve placed these commands in the <code class="language-plaintext highlighter-rouge">.xprofile</code> file in the <code class="language-plaintext highlighter-rouge">$HOME</code> directory.</p>

<div class="code-block">

<figure class="highlight"><pre><code class="language-bash" data-lang="bash"><span class="nv">$ </span><span class="nb">cat</span> ~/.xinitrc
<span class="c">#!/bin/sh</span>

<span class="c"># xinitrc runs automatically when you run startx.</span>

<span class="c"># There are some small but important commands that need to be run when we start</span>
<span class="c"># the graphical environment. I keep those commands in ~/.xprofile because that</span>
<span class="c"># file is run automatically if someone uses a display manager (login screen)</span>
<span class="c"># and so they are needed there. To prevent doubling up commands, I source them</span>
<span class="c"># here with the line below.</span>

<span class="o">[</span> <span class="nt">-f</span> ~/.xprofile <span class="o">]</span> <span class="o">&amp;&amp;</span> <span class="nb">.</span> ~/.xprofile

<span class="c"># Fix Gnome Apps Slow  Start due to failing services</span>
<span class="c"># Add this when you include flatpak in your system</span>
dbus-update-activation-environment <span class="nt">--systemd</span> DBUS_SESSION_BUS_ADDRESS DISPLAY XAUTHORITY

<span class="c"># Here we start dwm.</span>
<span class="c"># The loop is just to enable dwm's "restart" feature (mod+F2).</span>
<span class="c">#exec dwm</span>
ssh-agent dwm

<span class="nv">$ </span><span class="nb">cat</span> ~/.xprofile
<span class="c">#!/usr/bin/sh</span>

xset r rate 300 50 &amp;    <span class="c"># Speed xrate up</span>
unclutter &amp;     <span class="c"># Remove mouse when idle</span>
<span class="c">#xcompmgr &amp;     # xcompmgr for transparency</span>
picom <span class="nt">--config</span> ~/.config/picom/picom.conf &amp;
dunst &amp;                 <span class="c"># dunst for notifications</span>
<span class="c">#keymap &amp;       # some keyboard remap</span>
mpd &amp;
xrdb ~/.Xdefaults &amp;
setbg &amp;
setxkbmap <span class="nt">-model</span> thinkpad us,ir <span class="nt">-option</span> <span class="s2">"grp:shifts_toggle,caps:escape_shifted_capslock,altwin:prtsc_rwin,lv3:ralt_switch"</span> &amp;</code></pre></figure>

</div>

<p>As you can see, the keyboard settings are specified in the last line of the file. Each Display Manager has configuration files that it calls before the graphical environment starts. You can find the path to these files by searching online and place the keyboard settings in them so they’re applied automatically.</p>

<h2 id="xmodmap"><code class="language-plaintext highlighter-rouge">xmodmap</code></h2>

<p><code class="language-plaintext highlighter-rouge">xmodmap</code> is a tool for editing key layouts on the keyboard in <code class="language-plaintext highlighter-rouge">X11</code>. You may need to swap two keys on the keyboard and that option may not be available among <code class="language-plaintext highlighter-rouge">setxkbmap</code> choices. For this, you can use <code class="language-plaintext highlighter-rouge">xmodmap</code>:</p>

<div class="code-block">

<figure class="highlight"><pre><code class="language-bash" data-lang="bash"><span class="nv">$ </span>xmodmap <span class="nt">-e</span> <span class="s1">'keycode 135 = Super_R'</span></code></pre></figure>

</div>

<p>The above command converts the <code class="language-plaintext highlighter-rouge">Menu</code> key to the <code class="language-plaintext highlighter-rouge">Super</code> key, similar to what <code class="language-plaintext highlighter-rouge">setxkbmap -option altwin:menu_win</code> does. To find the code for each physical key on the keyboard, you can view the current keyboard layout list using <code class="language-plaintext highlighter-rouge">xmodmap -pk</code>.</p>

<div class="code-block">

<figure class="highlight"><pre><code class="language-bash" data-lang="bash"><span class="nv">$ </span>xmodmap <span class="nt">-pk</span>
There are 10 KeySyms per KeyCode<span class="p">;</span> KeyCodes range from 8 to 255.

    KeyCode Keysym <span class="o">(</span>Keysym<span class="o">)</span> ...
    Value   Value   <span class="o">(</span>Name<span class="o">)</span>  ...

      8
      9     0xff1b <span class="o">(</span>Escape<span class="o">)</span> ...
     10     0x0031 <span class="o">(</span>1<span class="o">)</span>  0x0021 <span class="o">(</span>exclam<span class="o">)</span> 0x10006f1 <span class="o">(</span>Farsi_1<span class="o">)</span> ...
...</code></pre></figure>

</div>

<p>The <code class="language-plaintext highlighter-rouge">xmodmap -pke</code> command gives you this list in a format usable by <code class="language-plaintext highlighter-rouge">xmodmap</code>.</p>

<p>A list of key names usable with <code class="language-plaintext highlighter-rouge">xmodmap</code> can be found on <a href="https://wiki.linuxquestions.org/wiki/List_of_Keysyms_Recognised_by_Xmodmap" target="_blank" rel="noopener noreferrer">this page</a>.</p>

<p>To find the code for a specific key, you can use <code class="language-plaintext highlighter-rouge">xev</code>. Simply run <code class="language-plaintext highlighter-rouge">xev</code> in the terminal and press the desired key to see its data.</p>

<div class="code-block">

<figure class="highlight"><pre><code class="language-bash" data-lang="bash"><span class="nv">$ </span>xev | <span class="nb">awk</span> <span class="nt">-F</span><span class="s1">'[ )]+'</span> <span class="s1">'/^KeyPress/ { a[NR+2] } NR in a { printf "%-3s %s\n", $5, $8 }'</span>
44  j
45  k
133 Super_L</code></pre></figure>

</div>

<p>There are many tools for working with the keyboard in <code class="language-plaintext highlighter-rouge">X11</code> server. For example, <code class="language-plaintext highlighter-rouge">xset -q</code> gives you information about the current configuration state:</p>

<div class="code-block">

<figure class="highlight"><pre><code class="language-bash" data-lang="bash"><span class="nv">$ </span>xset <span class="nt">-q</span>
Keyboard Control:
  auto repeat:  on    key click percent:  0    LED mask:  00000000
  XKB indicators:
    00: Caps Lock:   off    01: Num Lock:    off    02: Scroll Lock: off
  auto repeat delay:  300    repeat rate:  50
...</code></pre></figure>

</div>

<p>You can use it to check key states in scripts and configurations. You can also use <code class="language-plaintext highlighter-rouge">xdotool</code> to set the state of keyboard and mouse keys and the cursor position. <code class="language-plaintext highlighter-rouge">xdotool</code> is a tool for simulating input events in the <code class="language-plaintext highlighter-rouge">X11</code> server environment.</p>

<div class="code-block">

<figure class="highlight"><pre><code class="language-bash" data-lang="bash"><span class="nv">$ </span>xset <span class="nt">-q</span> | <span class="nb">grep</span> <span class="s2">"Caps Lock:</span><span class="se">\s</span><span class="s2">*on"</span> <span class="o">&amp;&amp;</span> xdotool key Caps_Lock</code></pre></figure>

</div>

<p>The above command checks the state of <code class="language-plaintext highlighter-rouge">CapsLock</code> and disables it if it is active.</p>

<h1 id="swaywm-on-wayland"><code class="language-plaintext highlighter-rouge">Swaywm</code> on <code class="language-plaintext highlighter-rouge">Wayland</code></h1>

<p>Keyboard configuration in the Wayland environment is the responsibility of the compositor, and there is no single tool or uniform method for doing it. Compositors on Wayland receive inputs without an intermediary layer and get data at a lower level from the kernel’s APIs.</p>

<p>Keyboard configuration in <code class="language-plaintext highlighter-rouge">Swaywm</code> is done in this window manager’s configuration file. By default this file is at <code class="language-plaintext highlighter-rouge">/etc/sway/config</code>. If a configuration file exists at <code class="language-plaintext highlighter-rouge">~/.config/sway/config</code>, that file takes higher priority.</p>

<p>My keyboard configuration for <code class="language-plaintext highlighter-rouge">Swaywm</code> in this file looks like this:</p>

<div class="code-block">

<figure class="highlight"><pre><code class="language-plaintext" data-lang="plaintext"># Use `man xkeyboard-config` to view the options and `setxkbmap -query|print` to check the model
input {
    type:keyboard {
        xkb_layout "us,ir"
        xkb_model "thinkpad"
        xkb_options "caps:escape_shifted_capslock,grp:shifts_toggle,altwin:prtsc_rwin,lv3:ralt_switch"
    }

    type:touchpad {
        # no click needed
        tap enabled
        # disable touchpad while typing
        dwt enabled
        # natural_scroll enabled
        middle_emulation enabled
    }
}</code></pre></figure>

</div>

<p>Which are the same settings I had for the <code class="language-plaintext highlighter-rouge">X11</code> server.</p>

<p>Tools like <code class="language-plaintext highlighter-rouge">xmodmap</code>, <code class="language-plaintext highlighter-rouge">setxkbmap</code>, <code class="language-plaintext highlighter-rouge">xset</code>, <code class="language-plaintext highlighter-rouge">xev</code>, and <code class="language-plaintext highlighter-rouge">xdotool</code> cannot be used on Wayland. The <a href="https://github.com/swaywm/sway/wiki/i3-Migration-Guide">Swaywm wiki</a> has a page about alternatives for common tools used in <code class="language-plaintext highlighter-rouge">X11</code>, which is a good guide. You can also visit <a href="https://github.com/natpen/awesome-wayland">this GitHub page</a> to find more tools for Wayland.</p>

<p>Instead of <code class="language-plaintext highlighter-rouge">xev</code> you can use <code class="language-plaintext highlighter-rouge">wev</code>, and instead of <code class="language-plaintext highlighter-rouge">xdotool</code> you can use <code class="language-plaintext highlighter-rouge">wtype</code>, <code class="language-plaintext highlighter-rouge">wlrctl</code>, <code class="language-plaintext highlighter-rouge">swaymsg seat &lt;seat&gt; cursor …</code>, or <code class="language-plaintext highlighter-rouge">ydotool</code>. However, no replacement for <code class="language-plaintext highlighter-rouge">xmodmap</code> has been introduced — instead, a custom layout can be used.</p>

<h1 id="creating-a-custom-keymap">Creating a Custom <code class="language-plaintext highlighter-rouge">keymap</code></h1>

<p>Even in the <code class="language-plaintext highlighter-rouge">X11</code> environment, applying keyboard settings permanently using the tools we’ve discussed may not fully meet all your needs. Creating a <code class="language-plaintext highlighter-rouge">keymap</code> tailored to your personal needs gives you the ability to make any change to the character layout.</p>

<p>Before going further, let me describe the problem that prompted writing this post. A friend on Mastodon was looking for a way to replace <code class="language-plaintext highlighter-rouge">/</code> with the character <code class="language-plaintext highlighter-rouge">ۀ</code> on the Persian layout. This character is not on the standard Persian keyboard and is an Arabic character. Its correct form in Persian is written by combining the two characters <code class="language-plaintext highlighter-rouge">ه</code> and <code class="language-plaintext highlighter-rouge">_ٔ</code>. However, due to printing limitations, they were forced to use this character.</p>

<p>The best way to create a custom <code class="language-plaintext highlighter-rouge">keymap</code> is to use one of the existing files in <code class="language-plaintext highlighter-rouge">/usr/share/X11/xkb/symbols</code> and edit it.</p>

<p>For example, the file for the Persian <code class="language-plaintext highlighter-rouge">keymap</code> has this pattern:</p>

<div class="code-block">

<figure class="highlight"><pre><code class="language-plaintext" data-lang="plaintext">$ cat /usr/share/X11/xkb/symbols/ir
// Iranian keyboard layout
...
default partial alphanumeric_keys
xkb_symbols "pes" {
    name[Group1]= "Persian";

    include "ir(pes_part_basic)"
    include "ir(pes_part_ext)"

    include "nbsp(zwnj2nb3nnb4)"
    include "level3(ralt_switch)"
};
...</code></pre></figure>

</div>

<p>In this file, the characters for each key are specified in an array corresponding to the different layers of that key. The row we’re interested in is <code class="language-plaintext highlighter-rouge">key &lt;AB10&gt; { [ slash, Arabic_question_mark, question ] };</code>. This means: pressing <code class="language-plaintext highlighter-rouge">AB10</code> (the <code class="language-plaintext highlighter-rouge">/</code> key) types <code class="language-plaintext highlighter-rouge">/</code>; pressing it with <code class="language-plaintext highlighter-rouge">Shift</code> types <code class="language-plaintext highlighter-rouge">؟</code>; and on layer 3 (with my configuration, using the right <code class="language-plaintext highlighter-rouge">Alt</code> key) it types <code class="language-plaintext highlighter-rouge">?</code>.</p>

<p>To apply the change and create our custom <code class="language-plaintext highlighter-rouge">keymap</code>, we make a copy of this file and apply the changes to it. We can then use this custom layout.</p>

<p>The Unicode code for the character <code class="language-plaintext highlighter-rouge">ۀ</code> is <code class="language-plaintext highlighter-rouge">U+06C0</code>, which in this file should be written as <code class="language-plaintext highlighter-rouge">0x10006c0</code>, following the same format as other Unicode characters:</p>

<div class="code-block">

<figure class="highlight"><pre><code class="language-bash" data-lang="bash"><span class="nv">$ </span><span class="nb">sudo cp</span> /usr/share/X11/xkb/symbols/<span class="o">{</span>ir,ir.bak<span class="o">}</span>

<span class="nv">$ </span><span class="nb">cat</span> /usr/share/X11/xkb/symbols/ir | <span class="nb">grep</span> <span class="nt">-n</span> slash
89:    key &lt;AB10&gt; <span class="o">{</span> <span class="o">[</span> slash,        Arabic_question_mark,   question    <span class="o">]</span> <span class="o">}</span><span class="p">;</span>
94:    key &lt;BKSL&gt; <span class="o">{</span> <span class="o">[</span> backslash,        bar,            0x1002010   <span class="o">]</span> <span class="o">}</span><span class="p">;</span>

<span class="nv">$ </span><span class="nb">sudo sed</span> <span class="nt">-i</span> <span class="s1">'s/\[ slash,/\[ 0x10006c0,/'</span> /usr/share/X11/xkb/symbols/my-ir

<span class="nv">$ </span><span class="nb">cat</span> /usr/share/X11/xkb/symbols/ir | <span class="nb">grep</span> <span class="nt">-n</span> 0x10006c0
89:    key &lt;AB10&gt; <span class="o">{</span> <span class="o">[</span> 0x10006c0,        Arabic_question_mark,   question    <span class="o">]</span> <span class="o">}</span><span class="p">;</span></code></pre></figure>

</div>

<p>To find the name or code of Unicode characters, you can visit <a href="https://unicode-table.com/en/" target="_blank" rel="noopener noreferrer">this website</a>.</p>

<p>Although editing files in <code class="language-plaintext highlighter-rouge">/usr/share/X11/xkb/symbols</code> allows you to customize your <code class="language-plaintext highlighter-rouge">keymap</code>, since <code class="language-plaintext highlighter-rouge">libxkbcommon 0.10.0</code> it has been possible to create personal configuration files in <code class="language-plaintext highlighter-rouge">$XDG_CONFIG_HOME/xkb</code> and in the folders <code class="language-plaintext highlighter-rouge">$XDG_CONFIG_HOME/xkb/rules/</code> and <code class="language-plaintext highlighter-rouge">$XDG_CONFIG_HOME/xkb/symbols</code>. Files in this path are loaded before the default files.</p>

<p>For example, to add an option to replace the <code class="language-plaintext highlighter-rouge">PrtSc</code> key with the <code class="language-plaintext highlighter-rouge">Menu</code> key, we create and configure the files <code class="language-plaintext highlighter-rouge">$XDG_CONFIG_HOME/xkb/rules/evdev</code> and <code class="language-plaintext highlighter-rouge">$XDG_CONFIG_HOME/xkb/symbols/custom</code> as follows:</p>

<div class="code-block">

<figure class="highlight"><pre><code class="language-bash" data-lang="bash"><span class="nv">$ </span><span class="nb">mkdir</span> <span class="nt">-p</span> ~/.config/xkb/<span class="o">{</span>symbols,rules<span class="o">}</span>
<span class="nv">$ </span><span class="nb">cd</span> ~/.config/xkb
<span class="nv">$ </span><span class="nb">cat</span> <span class="o">&gt;</span> rules/evdev <span class="o">&lt;&lt;</span> <span class="no">EOF</span><span class="sh">
! option = symbols
  custom:prtscmenu = +custom(prtscmenu)

! include %S/evdev
</span><span class="no">EOF
</span><span class="nv">$ </span><span class="nb">cat</span> <span class="o">&gt;</span> symbols/custom <span class="o">&lt;&lt;</span> <span class="no">EOF</span><span class="sh">
partial modifier_keys
xkb_symbols "prtscmenu" {
  key &lt;PRSC&gt; { [ Menu ] };
};
EOF</span></code></pre></figure>

</div>

<p>Then we need to enable/disable this option. For GNOME:</p>

<div class="code-block">

<figure class="highlight"><pre><code class="language-bash" data-lang="bash"><span class="c"># apply custom layout:</span>
<span class="nv">$ </span>gsettings <span class="nb">set </span>org.gnome.desktop.input-sources xkb-options <span class="s2">"['custom:prtscmenu']"</span>
<span class="c"># disable custom layout:</span>
<span class="nv">$ </span>gsettings <span class="nb">set </span>org.gnome.desktop.input-sources xkb-options <span class="s2">"[]"</span></code></pre></figure>

</div>

<p>Or to create the custom layout we made earlier:</p>

<div class="code-block">

<figure class="highlight"><pre><code class="language-bash" data-lang="bash"><span class="nv">$ </span><span class="nb">cat</span> <span class="o">&gt;</span> ~/.config/xkb/symbols/ir-mz <span class="o">&lt;&lt;</span> <span class="no">EOF</span><span class="sh">
default partial alphanumeric_keys
xkb_symbols "basic" {
    include "ir(pes)"
    include "level3(caps_switch)"
    name[Group1] = "Persian (IR, my custom)";
    key &lt;AB10&gt; { [ 0x10006c0, Arabic_question_mark, question ] };
};
EOF</span></code></pre></figure>

</div>

<p>I use the following file to swap the <code class="language-plaintext highlighter-rouge">چ</code> character (which is less commonly needed in certain workflows) with <code class="language-plaintext highlighter-rouge">/</code> in the standard Persian keyboard:</p>

<div class="code-block">

<figure class="highlight"><pre><code class="language-bash" data-lang="bash"><span class="nv">$ </span><span class="nb">cat</span> ~/.config/xkb/symbols/ir-mz
default partial alphanumeric_keys
xkb_symbols <span class="s2">"basic"</span> <span class="o">{</span>
    include <span class="s2">"ir(pes)"</span>
    name[Group1]<span class="o">=</span> <span class="s2">"Persian (IR, my custom layout)"</span><span class="p">;</span>
    key &lt;AB10&gt; <span class="o">{</span> <span class="o">[</span> Arabic_tcheh,		Arabic_question_mark,	question	<span class="o">]</span> <span class="o">}</span><span class="p">;</span>
    key &lt;AD12&gt; <span class="o">{</span> <span class="o">[</span> slash,	braceleft,		0x100202b	<span class="o">]</span> <span class="o">}</span><span class="p">;</span>
<span class="o">}</span><span class="p">;</span></code></pre></figure>

</div>

<p>To use this layout in <code class="language-plaintext highlighter-rouge">Swaywm</code>, add <code class="language-plaintext highlighter-rouge">input type:keyboard xkb_layout ir-mz</code> to the configuration file, or use this command:</p>

<div class="code-block">

<figure class="highlight"><pre><code class="language-bash" data-lang="bash">swaymsg input <span class="s2">"</span><span class="si">$(</span>swaymsg <span class="nt">-t</span> get_inputs | <span class="nb">grep</span> <span class="s1">'identifier.*keyboard'</span> | <span class="nb">cut</span> <span class="nt">-d</span><span class="s1">'"'</span> <span class="nt">-f4</span><span class="si">)</span><span class="s2">"</span> xkb_layout ir-mz</code></pre></figure>

</div>

<p>As mentioned, these settings are loaded and applied before the default paths. For more information, you can read <a href="https://who-t.blogspot.com/2020/02/user-specific-xkb-configuration-part-1.html?m=1" target="_blank" rel="noopener noreferrer">this page</a>.</p>]]></content><author><name>محمد زینلی</name><email>m@zmim.ir</email></author><category term="foss" /><category term="swaywm" /><category term="wayland" /><category term="xorg" /><category term="linux" /><summary type="html"><![CDATA[Keyboard configuration in GNU/Linux and customizing keyboard layers.]]></summary></entry><entry xml:lang="en"><title type="html">Adding terminfo Entries to a Remote Machine</title><link href="https://zmim.ir/en/terminfo/" rel="alternate" type="text/html" title="Adding terminfo Entries to a Remote Machine" /><published>2022-03-24T20:42:36+04:30</published><updated>2022-03-24T20:42:36+04:30</updated><id>https://zmim.ir/terminfo</id><content type="html" xml:base="https://zmim.ir/terminfo/"><![CDATA[<h1 id="transferring-files-between-android-and-gnulinux-and-the-terminfo-problem-with-foot-terminal">Transferring Files Between Android and GNU/Linux, and the <code class="language-plaintext highlighter-rouge">terminfo</code> Problem with <code class="language-plaintext highlighter-rouge">foot</code> Terminal</h1>

<p>I use the <a href="https://codeberg.org/dnkl/foot" target="_blank" rel="noopener noreferrer"><code class="language-plaintext highlighter-rouge">foot</code></a> terminal emulator on my system, which runs correctly on <code class="language-plaintext highlighter-rouge">wayland</code> and is very simple, fast, and minimal.
For transferring files between my Android phone and my GNU/Linux system, I use <code class="language-plaintext highlighter-rouge">scp</code> or <code class="language-plaintext highlighter-rouge">rsync</code> over <code class="language-plaintext highlighter-rouge">ssh</code>. If you’re interested, you can watch the tutorial video I made about this.</p>

<div class="video">
<iframe id="odysee-iframe" src="https://odysee.com/$/embed/ssh-on-termux/2c1fb60299e057dad14d1ffdd70f5bdbee16df88?r=CTpZDCJuEb8cZyCyCCpUEdw5D4LFZTkn" allowfullscreen=""></iframe>
</div>

<p>When I first SSH’d from the <code class="language-plaintext highlighter-rouge">foot</code> terminal into my Android phone, the terminal colors were not displayed correctly and every character I typed appeared twice.
After a bit of searching, I found that the problem was related to missing <code class="language-plaintext highlighter-rouge">terminfo</code> data on <code class="language-plaintext highlighter-rouge">termux</code>. Here you can see the missing colors and the repeated <code class="language-plaintext highlighter-rouge">zsh prompt</code> data on Termux after SSH, and compare it with the phone:</p>

<div style="text-align: center;">
    <img src="/terminfo/no-terminfo.png" style="max-width: 80%; margin: 10px;" alt="Missing `terminfo`" />
</div>

<h1 id="missing-terminfo-data-on-the-server">Missing <code class="language-plaintext highlighter-rouge">terminfo</code> Data on the Server</h1>

<p>You may have encountered these errors many times when running a terminal-based program on a server you’ve SSH’d into:</p>

<div class="code-block">

<figure class="highlight"><pre><code class="language-bash" data-lang="bash">Error opening terminal: xterm-kitty.
<span class="c"># OR</span>
WARNING: terminal is not fully functional
/etc/hosts  <span class="o">(</span>press RETURN<span class="o">)</span></code></pre></figure>

</div>

<p>These problems occur for the same reason I mentioned.</p>

<h1 id="what-to-do">What to Do?</h1>

<ul>
  <li>On the server, change the <code class="language-plaintext highlighter-rouge">$TERM</code> environment variable with <code class="language-plaintext highlighter-rouge">export TERM=xterm</code> and test whether the problem is resolved.</li>
  <li>Install the terminal emulator you’re using on the server as well. (For me this was not possible since I wanted to SSH from Termux.)</li>
  <li>Add the <code class="language-plaintext highlighter-rouge">terminfo</code> data to the server.</li>
</ul>

<h2 id="adding-terminfo-data-to-the-server">Adding <code class="language-plaintext highlighter-rouge">terminfo</code> Data to the Server</h2>

<ul>
  <li>If you have the <code class="language-plaintext highlighter-rouge">terminfo</code> data in a file, you can transfer it to the server and install it using <code class="language-plaintext highlighter-rouge">tic</code>.</li>
</ul>

<div class="code-block">

<figure class="highlight"><pre><code class="language-bash" data-lang="bash">rsync &lt;PATH-TO-YOUR.TERMINFO&gt; &lt;REMOTE-MACHINE&gt;:/tmp
ssh &lt;REMOTE-MACHINE&gt;
<span class="nb">sudo </span>tic /tmp/emu.terminfo</code></pre></figure>

</div>

<p>If you don’t have the data, you can view it and save it to a file using <code class="language-plaintext highlighter-rouge">infocmp &gt; foot.terminfo</code>.
For <code class="language-plaintext highlighter-rouge">foot</code>, the data looks like this:</p>

<div class="code-block">

<figure class="highlight"><pre><code class="language-bash" data-lang="bash">infocmp
<span class="c">#	Reconstructed via infocmp from file: /usr/share/terminfo/f/foot</span>
foot|foot terminal emulator,
	am, bce, bw, ccc, hs, mir, msgr, npc, xenl,
	colors#0x100, cols#80, it#8, lines#24, pairs#0x10000,
	<span class="nv">acsc</span><span class="o">=</span><span class="sb">``</span>aaffggiijjkkllmmnnooppqqrrssttuuvvwwxxyyzz<span class="o">{{||}}</span>~~,
	<span class="nv">bel</span><span class="o">=</span>^G, <span class="nv">blink</span><span class="o">=</span><span class="se">\E</span><span class="o">[</span>5m, <span class="nv">bold</span><span class="o">=</span><span class="se">\E</span><span class="o">[</span>1m, <span class="nv">cbt</span><span class="o">=</span><span class="se">\E</span><span class="o">[</span>Z, <span class="nv">civis</span><span class="o">=</span><span class="se">\E</span><span class="o">[</span>?25l,
	<span class="nv">clear</span><span class="o">=</span><span class="se">\E</span><span class="o">[</span>H<span class="se">\E</span><span class="o">[</span>2J, <span class="nv">cnorm</span><span class="o">=</span><span class="se">\E</span><span class="o">[</span>?12l<span class="se">\E</span><span class="o">[</span>?25h, <span class="nv">cr</span><span class="o">=</span><span class="se">\r</span>,
	<span class="nv">csr</span><span class="o">=</span><span class="se">\E</span><span class="o">[</span>%i%p1%d<span class="p">;</span>%p2%dr, <span class="nv">cub</span><span class="o">=</span><span class="se">\E</span><span class="o">[</span>%p1%dD, <span class="nv">cub1</span><span class="o">=</span>^H,
	<span class="nv">cud</span><span class="o">=</span><span class="se">\E</span><span class="o">[</span>%p1%dB, <span class="nv">cud1</span><span class="o">=</span><span class="se">\n</span>, <span class="nv">cuf</span><span class="o">=</span><span class="se">\E</span><span class="o">[</span>%p1%dC, <span class="nv">cuf1</span><span class="o">=</span><span class="se">\E</span><span class="o">[</span>C,
	<span class="nv">cup</span><span class="o">=</span><span class="se">\E</span><span class="o">[</span>%i%p1%d<span class="p">;</span>%p2%dH, <span class="nv">cuu</span><span class="o">=</span><span class="se">\E</span><span class="o">[</span>%p1%dA, <span class="nv">cuu1</span><span class="o">=</span><span class="se">\E</span><span class="o">[</span>A,
	<span class="nv">cvvis</span><span class="o">=</span><span class="se">\E</span><span class="o">[</span>?12<span class="p">;</span>25h, <span class="nv">dch</span><span class="o">=</span><span class="se">\E</span><span class="o">[</span>%p1%dP, <span class="nv">dch1</span><span class="o">=</span><span class="se">\E</span><span class="o">[</span>P, <span class="nv">dim</span><span class="o">=</span><span class="se">\E</span><span class="o">[</span>2m,
	<span class="nv">dl</span><span class="o">=</span><span class="se">\E</span><span class="o">[</span>%p1%dM, <span class="nv">dl1</span><span class="o">=</span><span class="se">\E</span><span class="o">[</span>M, <span class="nv">ech</span><span class="o">=</span><span class="se">\E</span><span class="o">[</span>%p1%dX, <span class="nv">ed</span><span class="o">=</span><span class="se">\E</span><span class="o">[</span>J, <span class="nv">el</span><span class="o">=</span><span class="se">\E</span><span class="o">[</span>K,
	<span class="nv">el1</span><span class="o">=</span><span class="se">\E</span><span class="o">[</span>1K, <span class="nv">flash</span><span class="o">=</span><span class="se">\E</span><span class="o">]</span>555<span class="se">\E\\</span>, <span class="nv">home</span><span class="o">=</span><span class="se">\E</span><span class="o">[</span>H, <span class="nv">hpa</span><span class="o">=</span><span class="se">\E</span><span class="o">[</span>%i%p1%dG,
	<span class="nv">ht</span><span class="o">=</span>^I, <span class="nv">hts</span><span class="o">=</span><span class="se">\E</span>H, <span class="nv">ich</span><span class="o">=</span><span class="se">\E</span><span class="o">[</span>%p1%d@, <span class="nv">ich1</span><span class="o">=</span><span class="se">\E</span><span class="o">[</span>@, <span class="nv">il</span><span class="o">=</span><span class="se">\E</span><span class="o">[</span>%p1%dL,
	<span class="nv">il1</span><span class="o">=</span><span class="se">\E</span><span class="o">[</span>L, <span class="nv">ind</span><span class="o">=</span><span class="se">\n</span>, <span class="nv">indn</span><span class="o">=</span><span class="se">\E</span><span class="o">[</span>%p1%dS,
	<span class="nv">initc</span><span class="o">=</span><span class="se">\E</span><span class="o">]</span>4<span class="p">;</span>%p1%d<span class="p">;</span>rgb:%p2%<span class="o">{</span>255<span class="o">}</span>%<span class="k">*</span>%<span class="o">{</span>1000<span class="o">}</span>%/%2.2X/%p3%<span class="o">{</span>255<span class="o">}</span>%<span class="k">*</span>%<span class="o">{</span>1000<span class="o">}</span>%/%2.2X/%p4%<span class="o">{</span>255<span class="o">}</span>%<span class="k">*</span>%<span class="o">{</span>1000<span class="o">}</span>%/%2.2X<span class="se">\E\\</span>,
	<span class="nv">invis</span><span class="o">=</span><span class="se">\E</span><span class="o">[</span>8m, <span class="nv">is2</span><span class="o">=</span><span class="se">\E</span><span class="o">[!</span>p<span class="se">\E</span><span class="o">[</span>?3<span class="p">;</span>4l<span class="se">\E</span><span class="o">[</span>4l<span class="se">\E</span><span class="o">&gt;</span>, <span class="nv">kDC</span><span class="o">=</span><span class="se">\E</span><span class="o">[</span>3<span class="p">;</span>2~,
	<span class="nv">kEND</span><span class="o">=</span><span class="se">\E</span><span class="o">[</span>1<span class="p">;</span>2F, <span class="nv">kHOM</span><span class="o">=</span><span class="se">\E</span><span class="o">[</span>1<span class="p">;</span>2H, <span class="nv">kIC</span><span class="o">=</span><span class="se">\E</span><span class="o">[</span>2<span class="p">;</span>2~, <span class="nv">kLFT</span><span class="o">=</span><span class="se">\E</span><span class="o">[</span>1<span class="p">;</span>2D,
	<span class="nv">kNXT</span><span class="o">=</span><span class="se">\E</span><span class="o">[</span>6<span class="p">;</span>2~, <span class="nv">kPRV</span><span class="o">=</span><span class="se">\E</span><span class="o">[</span>5<span class="p">;</span>2~, <span class="nv">kRIT</span><span class="o">=</span><span class="se">\E</span><span class="o">[</span>1<span class="p">;</span>2C, <span class="nv">kbs</span><span class="o">=</span>^?,
	<span class="nv">kcbt</span><span class="o">=</span><span class="se">\E</span><span class="o">[</span>Z, <span class="nv">kcub1</span><span class="o">=</span><span class="se">\E</span>OD, <span class="nv">kcud1</span><span class="o">=</span><span class="se">\E</span>OB, <span class="nv">kcuf1</span><span class="o">=</span><span class="se">\E</span>OC, <span class="nv">kcuu1</span><span class="o">=</span><span class="se">\E</span>OA,
	<span class="nv">kdch1</span><span class="o">=</span><span class="se">\E</span><span class="o">[</span>3~, <span class="nv">kend</span><span class="o">=</span><span class="se">\E</span>OF, <span class="nv">kf1</span><span class="o">=</span><span class="se">\E</span>OP, <span class="nv">kf10</span><span class="o">=</span><span class="se">\E</span><span class="o">[</span>21~, <span class="nv">kf11</span><span class="o">=</span><span class="se">\E</span><span class="o">[</span>23~,
	<span class="nv">kf12</span><span class="o">=</span><span class="se">\E</span><span class="o">[</span>24~, <span class="nv">kf13</span><span class="o">=</span><span class="se">\E</span><span class="o">[</span>1<span class="p">;</span>2P, <span class="nv">kf14</span><span class="o">=</span><span class="se">\E</span><span class="o">[</span>1<span class="p">;</span>2Q, <span class="nv">kf15</span><span class="o">=</span><span class="se">\E</span><span class="o">[</span>1<span class="p">;</span>2R,
	<span class="nv">kf16</span><span class="o">=</span><span class="se">\E</span><span class="o">[</span>1<span class="p">;</span>2S, <span class="nv">kf17</span><span class="o">=</span><span class="se">\E</span><span class="o">[</span>15<span class="p">;</span>2~, <span class="nv">kf18</span><span class="o">=</span><span class="se">\E</span><span class="o">[</span>17<span class="p">;</span>2~,
	<span class="nv">kf19</span><span class="o">=</span><span class="se">\E</span><span class="o">[</span>18<span class="p">;</span>2~, <span class="nv">kf2</span><span class="o">=</span><span class="se">\E</span>OQ, <span class="nv">kf20</span><span class="o">=</span><span class="se">\E</span><span class="o">[</span>19<span class="p">;</span>2~, <span class="nv">kf21</span><span class="o">=</span><span class="se">\E</span><span class="o">[</span>20<span class="p">;</span>2~,
	<span class="nv">kf22</span><span class="o">=</span><span class="se">\E</span><span class="o">[</span>21<span class="p">;</span>2~, <span class="nv">kf23</span><span class="o">=</span><span class="se">\E</span><span class="o">[</span>23<span class="p">;</span>2~, <span class="nv">kf24</span><span class="o">=</span><span class="se">\E</span><span class="o">[</span>24<span class="p">;</span>2~,
	<span class="nv">kf25</span><span class="o">=</span><span class="se">\E</span><span class="o">[</span>1<span class="p">;</span>5P, <span class="nv">kf26</span><span class="o">=</span><span class="se">\E</span><span class="o">[</span>1<span class="p">;</span>5Q, <span class="nv">kf27</span><span class="o">=</span><span class="se">\E</span><span class="o">[</span>1<span class="p">;</span>5R, <span class="nv">kf28</span><span class="o">=</span><span class="se">\E</span><span class="o">[</span>1<span class="p">;</span>5S,
	<span class="nv">kf29</span><span class="o">=</span><span class="se">\E</span><span class="o">[</span>15<span class="p">;</span>5~, <span class="nv">kf3</span><span class="o">=</span><span class="se">\E</span>OR, <span class="nv">kf30</span><span class="o">=</span><span class="se">\E</span><span class="o">[</span>17<span class="p">;</span>5~, <span class="nv">kf31</span><span class="o">=</span><span class="se">\E</span><span class="o">[</span>18<span class="p">;</span>5~,
	<span class="nv">kf32</span><span class="o">=</span><span class="se">\E</span><span class="o">[</span>19<span class="p">;</span>5~, <span class="nv">kf33</span><span class="o">=</span><span class="se">\E</span><span class="o">[</span>20<span class="p">;</span>5~, <span class="nv">kf34</span><span class="o">=</span><span class="se">\E</span><span class="o">[</span>21<span class="p">;</span>5~,
	<span class="nv">kf35</span><span class="o">=</span><span class="se">\E</span><span class="o">[</span>23<span class="p">;</span>5~, <span class="nv">kf36</span><span class="o">=</span><span class="se">\E</span><span class="o">[</span>24<span class="p">;</span>5~, <span class="nv">kf37</span><span class="o">=</span><span class="se">\E</span><span class="o">[</span>1<span class="p">;</span>6P, <span class="nv">kf38</span><span class="o">=</span><span class="se">\E</span><span class="o">[</span>1<span class="p">;</span>6Q,
	<span class="nv">kf39</span><span class="o">=</span><span class="se">\E</span><span class="o">[</span>1<span class="p">;</span>6R, <span class="nv">kf4</span><span class="o">=</span><span class="se">\E</span>OS, <span class="nv">kf40</span><span class="o">=</span><span class="se">\E</span><span class="o">[</span>1<span class="p">;</span>6S, <span class="nv">kf41</span><span class="o">=</span><span class="se">\E</span><span class="o">[</span>15<span class="p">;</span>6~,
	<span class="nv">kf42</span><span class="o">=</span><span class="se">\E</span><span class="o">[</span>17<span class="p">;</span>6~, <span class="nv">kf43</span><span class="o">=</span><span class="se">\E</span><span class="o">[</span>18<span class="p">;</span>6~, <span class="nv">kf44</span><span class="o">=</span><span class="se">\E</span><span class="o">[</span>19<span class="p">;</span>6~,
	<span class="nv">kf45</span><span class="o">=</span><span class="se">\E</span><span class="o">[</span>20<span class="p">;</span>6~, <span class="nv">kf46</span><span class="o">=</span><span class="se">\E</span><span class="o">[</span>21<span class="p">;</span>6~, <span class="nv">kf47</span><span class="o">=</span><span class="se">\E</span><span class="o">[</span>23<span class="p">;</span>6~,
	<span class="nv">kf48</span><span class="o">=</span><span class="se">\E</span><span class="o">[</span>24<span class="p">;</span>6~, <span class="nv">kf49</span><span class="o">=</span><span class="se">\E</span><span class="o">[</span>1<span class="p">;</span>3P, <span class="nv">kf5</span><span class="o">=</span><span class="se">\E</span><span class="o">[</span>15~, <span class="nv">kf50</span><span class="o">=</span><span class="se">\E</span><span class="o">[</span>1<span class="p">;</span>3Q,
	<span class="nv">kf51</span><span class="o">=</span><span class="se">\E</span><span class="o">[</span>1<span class="p">;</span>3R, <span class="nv">kf52</span><span class="o">=</span><span class="se">\E</span><span class="o">[</span>1<span class="p">;</span>3S, <span class="nv">kf53</span><span class="o">=</span><span class="se">\E</span><span class="o">[</span>15<span class="p">;</span>3~, <span class="nv">kf54</span><span class="o">=</span><span class="se">\E</span><span class="o">[</span>17<span class="p">;</span>3~,
	<span class="nv">kf55</span><span class="o">=</span><span class="se">\E</span><span class="o">[</span>18<span class="p">;</span>3~, <span class="nv">kf56</span><span class="o">=</span><span class="se">\E</span><span class="o">[</span>19<span class="p">;</span>3~, <span class="nv">kf57</span><span class="o">=</span><span class="se">\E</span><span class="o">[</span>20<span class="p">;</span>3~,
	<span class="nv">kf58</span><span class="o">=</span><span class="se">\E</span><span class="o">[</span>21<span class="p">;</span>3~, <span class="nv">kf59</span><span class="o">=</span><span class="se">\E</span><span class="o">[</span>23<span class="p">;</span>3~, <span class="nv">kf6</span><span class="o">=</span><span class="se">\E</span><span class="o">[</span>17~, <span class="nv">kf60</span><span class="o">=</span><span class="se">\E</span><span class="o">[</span>24<span class="p">;</span>3~,
	<span class="nv">kf61</span><span class="o">=</span><span class="se">\E</span><span class="o">[</span>1<span class="p">;</span>4P, <span class="nv">kf62</span><span class="o">=</span><span class="se">\E</span><span class="o">[</span>1<span class="p">;</span>4Q, <span class="nv">kf63</span><span class="o">=</span><span class="se">\E</span><span class="o">[</span>1<span class="p">;</span>4R, <span class="nv">kf7</span><span class="o">=</span><span class="se">\E</span><span class="o">[</span>18~,
	<span class="nv">kf8</span><span class="o">=</span><span class="se">\E</span><span class="o">[</span>19~, <span class="nv">kf9</span><span class="o">=</span><span class="se">\E</span><span class="o">[</span>20~, <span class="nv">khome</span><span class="o">=</span><span class="se">\E</span>OH, <span class="nv">kich1</span><span class="o">=</span><span class="se">\E</span><span class="o">[</span>2~,
	<span class="nv">kind</span><span class="o">=</span><span class="se">\E</span><span class="o">[</span>1<span class="p">;</span>2B, <span class="nv">kmous</span><span class="o">=</span><span class="se">\E</span><span class="o">[</span>&lt;, <span class="nv">knp</span><span class="o">=</span><span class="se">\E</span><span class="o">[</span>6~, <span class="nv">kpp</span><span class="o">=</span><span class="se">\E</span><span class="o">[</span>5~,
	<span class="nv">kri</span><span class="o">=</span><span class="se">\E</span><span class="o">[</span>1<span class="p">;</span>2A, <span class="nv">oc</span><span class="o">=</span><span class="se">\E</span><span class="o">]</span>104<span class="se">\E\\</span>, <span class="nv">op</span><span class="o">=</span><span class="se">\E</span><span class="o">[</span>39<span class="p">;</span>49m, <span class="nv">rc</span><span class="o">=</span><span class="se">\E</span>8,
	<span class="nv">rep</span><span class="o">=</span>%p1%c<span class="se">\E</span><span class="o">[</span>%p2%<span class="o">{</span>1<span class="o">}</span>%-%db, <span class="nv">rev</span><span class="o">=</span><span class="se">\E</span><span class="o">[</span>7m, <span class="nv">ri</span><span class="o">=</span><span class="se">\E</span>M,
	<span class="nv">rin</span><span class="o">=</span><span class="se">\E</span><span class="o">[</span>%p1%dT, <span class="nv">ritm</span><span class="o">=</span><span class="se">\E</span><span class="o">[</span>23m, <span class="nv">rmacs</span><span class="o">=</span><span class="se">\E</span><span class="o">(</span>B, <span class="nv">rmam</span><span class="o">=</span><span class="se">\E</span><span class="o">[</span>?7l,
	<span class="nv">rmcup</span><span class="o">=</span><span class="se">\E</span><span class="o">[</span>?1049l<span class="se">\E</span><span class="o">[</span>23<span class="p">;</span>0<span class="p">;</span>0t, <span class="nv">rmir</span><span class="o">=</span><span class="se">\E</span><span class="o">[</span>4l, <span class="nv">rmkx</span><span class="o">=</span><span class="se">\E</span><span class="o">[</span>?1l<span class="se">\E</span><span class="o">&gt;</span>,
	<span class="nv">rmso</span><span class="o">=</span><span class="se">\E</span><span class="o">[</span>27m, <span class="nv">rmul</span><span class="o">=</span><span class="se">\E</span><span class="o">[</span>24m, <span class="nv">rs1</span><span class="o">=</span><span class="se">\E</span>c,
	<span class="nv">rs2</span><span class="o">=</span><span class="se">\E</span><span class="o">[!</span>p<span class="se">\E</span><span class="o">[</span>?3<span class="p">;</span>4l<span class="se">\E</span><span class="o">[</span>4l<span class="se">\E</span><span class="o">&gt;</span>, <span class="nv">sc</span><span class="o">=</span><span class="se">\E</span>7,
	<span class="nv">setab</span><span class="o">=</span><span class="se">\E</span><span class="o">[</span>%?%p1%<span class="o">{</span>8<span class="o">}</span>%&lt;%t4%p1%d%e%p1%<span class="o">{</span>16<span class="o">}</span>%&lt;%t10%p1%<span class="o">{</span>8<span class="o">}</span>%-%d%e48:5:%p1%d%<span class="p">;</span>m,
	<span class="nv">setaf</span><span class="o">=</span><span class="se">\E</span><span class="o">[</span>%?%p1%<span class="o">{</span>8<span class="o">}</span>%&lt;%t3%p1%d%e%p1%<span class="o">{</span>16<span class="o">}</span>%&lt;%t9%p1%<span class="o">{</span>8<span class="o">}</span>%-%d%e38:5:%p1%d%<span class="p">;</span>m,
	<span class="nv">sgr</span><span class="o">=</span>%?%p9%t<span class="se">\E</span><span class="o">(</span>0%e<span class="se">\E</span><span class="o">(</span>B%<span class="p">;</span><span class="se">\E</span><span class="o">[</span>0%?%p6%t<span class="p">;</span>1%<span class="p">;</span>%?%p5%t<span class="p">;</span>2%<span class="p">;</span>%?%p2%t<span class="p">;</span>4%<span class="p">;</span>%?%p1%p3%|%t<span class="p">;</span>7%<span class="p">;</span>%?%p4%t<span class="p">;</span>5%<span class="p">;</span>%?%p7%t<span class="p">;</span>8%<span class="p">;</span>m,
	<span class="nv">sgr0</span><span class="o">=</span><span class="se">\E</span><span class="o">(</span>B<span class="se">\E</span><span class="o">[</span>m, <span class="nv">sitm</span><span class="o">=</span><span class="se">\E</span><span class="o">[</span>3m, <span class="nv">smacs</span><span class="o">=</span><span class="se">\E</span><span class="o">(</span>0, <span class="nv">smam</span><span class="o">=</span><span class="se">\E</span><span class="o">[</span>?7h,
	<span class="nv">smcup</span><span class="o">=</span><span class="se">\E</span><span class="o">[</span>?1049h<span class="se">\E</span><span class="o">[</span>22<span class="p">;</span>0<span class="p">;</span>0t, <span class="nv">smir</span><span class="o">=</span><span class="se">\E</span><span class="o">[</span>4h, <span class="nv">smkx</span><span class="o">=</span><span class="se">\E</span><span class="o">[</span>?1h<span class="se">\E</span><span class="o">=</span>,
	<span class="nv">smso</span><span class="o">=</span><span class="se">\E</span><span class="o">[</span>7m, <span class="nv">smul</span><span class="o">=</span><span class="se">\E</span><span class="o">[</span>4m, <span class="nv">tbc</span><span class="o">=</span><span class="se">\E</span><span class="o">[</span>3g, <span class="nv">u6</span><span class="o">=</span><span class="se">\E</span><span class="o">[</span>%i%d<span class="p">;</span>%dR,
	<span class="nv">u7</span><span class="o">=</span><span class="se">\E</span><span class="o">[</span>6n, <span class="nv">u8</span><span class="o">=</span><span class="se">\E</span><span class="o">[</span>?%[<span class="p">;</span>0123456789]c, <span class="nv">u9</span><span class="o">=</span><span class="se">\E</span><span class="o">[</span>c,
	<span class="nv">vpa</span><span class="o">=</span><span class="se">\E</span><span class="o">[</span>%i%p1%dd,</code></pre></figure>

</div>

<ul>
  <li>The simpler way is to generate the data and pipe it directly to the server for installation:</li>
</ul>

<div class="code-block">

<figure class="highlight"><pre><code class="language-bash" data-lang="bash">infocmp | ssh &lt;REMOTE-MACHINE&gt; <span class="s2">"tic -x /dev/stdin"</span></code></pre></figure>

</div>

<ul>
  <li>But if your server is old or for some reason <code class="language-plaintext highlighter-rouge">tic</code> cannot access <code class="language-plaintext highlighter-rouge">/dev/stdin</code>, you can write the data directly to a file on the server and then install it:</li>
</ul>

<div class="code-block">

<figure class="highlight"><pre><code class="language-bash" data-lang="bash">infocmp | ssh &lt;REMOTE-MACHINE&gt; <span class="s2">"cat &gt; /tmp/terminfo &amp;&amp; tic -x /tmp/terminfo; rm /tmp/terminfo"</span></code></pre></figure>

</div>

<p>After adding <code class="language-plaintext highlighter-rouge">foot</code> data to <code class="language-plaintext highlighter-rouge">termux</code>, my problem was resolved.</p>

<div style="text-align: center;">
    <img src="/terminfo/with-terminfo.png" style="max-width: 80%; margin: 10px;" alt="Colors displaying correctly after adding `terminfo` data" />
</div>

<h1 id="resources">Resources:</h1>

<ul>
  <li><a href="https://sw.kovidgoyal.net/kitty/faq/#i-get-errors-about-the-terminal-being-unknown-or-opening-the-terminal-failing-when-sshing-into-a-different-computer" target="_blank" rel="noopener noreferrer">kitty FAQ</a></li>
  <li><a href="https://codeberg.org/dnkl/foot" target="_blank" rel="noopener noreferrer">foot repository</a></li>
</ul>]]></content><author><name>محمد زینلی</name><email>m@zmim.ir</email></author><category term="foss" /><category term="termux" /><category term="ssh" /><category term="linux" /><summary type="html"><![CDATA[Depending on the terminal emulator you use, some terminal-based programs may not work correctly on a server you've SSH'd into, and you may need to add terminfo entries to the server.]]></summary></entry><entry xml:lang="en"><title type="html">Installing Ubuntu 20.04 on a Lenovo ThinkPad 10 Tablet</title><link href="https://zmim.ir/en/ubuntu-on-tablet/" rel="alternate" type="text/html" title="Installing Ubuntu 20.04 on a Lenovo ThinkPad 10 Tablet" /><published>2021-02-13T10:20:00+03:30</published><updated>2021-02-13T10:20:00+03:30</updated><id>https://zmim.ir/ubuntu-on-tablet</id><content type="html" xml:base="https://zmim.ir/ubuntu-on-tablet/"><![CDATA[<h1 id="background">Background</h1>

<p>A friend of mine has a Lenovo ThinkPad 10 tablet and, wanting to install GNU/Linux on it, asked for my help. I was also curious to see how well different desktop environments work with touchscreens.</p>

<p>To avoid having to reformat a USB drive every time you want to boot a live ISO, you can use <a href="https://github.com/ventoy/Ventoy" target="_blank" rel="noopener noreferrer">ventoy</a>.</p>

<p>After testing several options, we decided to install Ubuntu. Since it was my friend’s first time using GNU/Linux, we decided to go step by step and I would explain the process at each stage. But right at the start, things got stuck. I tried several times using <code class="language-plaintext highlighter-rouge">Rufus</code> to create a bootable USB from the <code class="language-plaintext highlighter-rouge">ISO</code> file. Even though I had set USB as the first boot priority in <code class="language-plaintext highlighter-rouge">BIOS</code> settings (accessible by pressing the power button and volume-up simultaneously, where you can also change boot settings), disabled <code class="language-plaintext highlighter-rouge">Secure Boot</code> and <code class="language-plaintext highlighter-rouge">Fast Boot</code> — the system kept booting from the hard drive and wouldn’t recognize the <code class="language-plaintext highlighter-rouge">USB drive</code> as <code class="language-plaintext highlighter-rouge">bootable</code>.</p>

<p>I also tried the familiar method of writing the <code class="language-plaintext highlighter-rouge">ISO</code> to <code class="language-plaintext highlighter-rouge">USB</code> using <code class="language-plaintext highlighter-rouge">dd</code>, but while it booted fine on a laptop, it had problems on the tablet.</p>

<div class="code-block">

<figure class="highlight"><pre><code class="language-bash" data-lang="bash"><span class="nb">dd </span><span class="k">if</span><span class="o">=</span>&lt;PATH-TO-A-LIVE-ISO&gt; <span class="nv">of</span><span class="o">=</span>/dev/sdx <span class="nv">status</span><span class="o">=</span><span class="s2">"progress"</span></code></pre></figure>

</div>

<p>Here <code class="language-plaintext highlighter-rouge">sdx</code> is the block device corresponding to your <code class="language-plaintext highlighter-rouge">USB drive</code>, which you can find using <code class="language-plaintext highlighter-rouge">lsblk</code>.</p>

<p>After a bit of searching and checking the tablet’s specs, I realized this system uses a 32-bit <code class="language-plaintext highlighter-rouge">UEFI</code>, which is exactly why the <code class="language-plaintext highlighter-rouge">USB</code> was not booting.</p>

<h1 id="what-to-do">What to Do?</h1>

<p>The answer is simple:</p>

<ul>
  <li>Format the <code class="language-plaintext highlighter-rouge">USB drive</code> as <code class="language-plaintext highlighter-rouge">fat32</code>.</li>
  <li>Manually copy the contents of the <code class="language-plaintext highlighter-rouge">ISO</code> file onto it.</li>
</ul>

<p>To <code class="language-plaintext highlighter-rouge">mount</code> an <code class="language-plaintext highlighter-rouge">ISO</code> file on GNU/Linux, use the following command:</p>
<div class="code-block">

<figure class="highlight"><pre><code class="language-bash" data-lang="bash"><span class="nb">sudo </span>mount <span class="nt">-o</span> loop &lt;PATH-TO-A-LIVE-ISO&gt; &lt;PATH-TO-THE-FOLDER-YOU-WANT-TO-MOUNT-THE-ISO&gt;</code></pre></figure>

</div>

<ul>
  <li>Download the <a href="https://github.com/hirotakaster/baytail-bootia32.efi" target="_blank" rel="noopener noreferrer"><code class="language-plaintext highlighter-rouge">bootia32.efi</code></a> file from the GitHub repository or from <a href="/en/ubuntu-on-tablet/bootia32.efi">here</a> and copy it to the <code class="language-plaintext highlighter-rouge">/EFI/BOOT</code> folder.</li>
  <li>Copy the <code class="language-plaintext highlighter-rouge">grub.cfg</code> file from the <code class="language-plaintext highlighter-rouge">/boot/grub</code> folder and place it in the root of the <code class="language-plaintext highlighter-rouge">USB</code> (<code class="language-plaintext highlighter-rouge">/</code>).</li>
  <li>Disable <code class="language-plaintext highlighter-rouge">Fast Boot</code> and <code class="language-plaintext highlighter-rouge">Secure Boot</code>, then boot the system from the <code class="language-plaintext highlighter-rouge">USB</code>.</li>
</ul>]]></content><author><name>محمد زینلی</name><email>m@zmim.ir</email></author><category term="foss" /><category term="ubuntu" /><category term="touch" /><summary type="html"><![CDATA[Booting a device with 32-bit UEFI from USB]]></summary></entry><entry xml:lang="fa"><title type="html">ارثیه</title><link href="https://zmim.ir/en/heritage/" rel="alternate" type="text/html" title="ارثیه" /><published>2021-01-25T14:20:00+03:30</published><updated>2021-01-25T14:20:00+03:30</updated><id>https://zmim.ir/heritage</id><content type="html" xml:base="https://zmim.ir/heritage/"><![CDATA[<p>خداداد</p>

<p>انگار می توانست همه آنچه را که ته باغ بالای چاه کنار اتاقک در کله پیرمرد گذشته بود بخواند. به خاطرش آمد نوه‌اش هم همانطور خیره نگاهش کرده بود. معلوم بود پسرش همان وقت تصمیم نگرفته بود. خوب که نگاه کرد مطمئن شد پسرش همان چشم‌ها را داشت. همان‌ها که پرهیز می‌کرد از دیدنشان در آینه. همان چشم‌های مریم که هر بار به آینه نگاه می‌کرد، با غضب نگاهش می‌کردند. مریم مادرش بود انگار همه این اتفاق‌ها را او سال‌ها پیش رقم زده بود با یک نفرین. همان موقع که روی تخت افتاده بود و بعد از بیست سال به هوای اینکه می‌خواست توی ده بمیرد پسرش را کشانده بود آنجا. زن سوم علیخان بود بعد از دو تا زن که هیچ کدام بچه نیاورده بودند. همه می‌گفتند اجاق علیخان کور است و بی وارث می‌ماند. علیخان چهل و پنج سالی داشت که مریم را گرفته بود. همان‌جا توی ده آبستن شده بود و تنها پسر علیخان را آورده بود.</p>

<p>مریم</p>

<p>آسمان ابر گرفته و هوا نمدار بود. مردها همگی رفته بودند دنبال گرگ‌ها، که آن سال بد جوری جری شده بودند و تا پای آغل‌ها هم آمده بودند. نه پیش از آن سال دیده بودش و نه بعد از آن. حالا بعد از بیست سال روی تخت افتاده بود و می‌دانست که امشب را به صبح نمی‌رساند. صدای جیرجیرک‌ها را می‌شنید و بوی نم باغچه را، که باد همراه عطرچوب‌های تازه و گل‌هایی که نمی‌شناخت می‌آورد. تنها چیزی که می‌خواست این بود که یک بار دیگر قیافه سلیمان را ببیند و بی هیچ بهانه آوردنی همه چیز را برای خداداد گفته بود.</p>

<p>خداداد</p>

<p>خداداد آشفته پالتویش را برداشت و از در جلوی باغچه زد بیرون. تا میدانگاهی هیچ کس نبود. بی معطلی راه افتاد. می‌خواست تا از خانه اربابی و همه آنچه شنیده بود بگریزد. باد بوته چرخه ای را توی کوچه خالی می‌غلتاند. پایش توی گل کنار جو رفت و سر خورد. بلند شد و لباسش را نگاه کرد، تا زیر سینه‌اش گل شده بود. دور میدانگاهی یک قهوه خانه بود و خوار و بار فروشی که همه احتیاحات ابتدایی اهالی را رفع می‌کرد، نانوایی و عطاری که قیافه‌اش را توی شیشه آن نگاه کرد. لب‌های باریک و کشیده، چانه تیز، موهای بورش و با خودش فکر کرد: «غیر از چشم‌هام». یاد سلیمان افتاد و لرزش گرفت. راهش را ادامه داد. جلوی قهوه خانه داشتند خون‌های گوسفند را می‌شستند. سگ سیاهی با گوش‌های بریده شده آشغال‌های لاشه را به دندان می‌کشید. پسر بچه‌ای هم با لباس قرمز آستین پاره و کله از ته تراشیده چمباتمه زده بود. صورتی کثیف و آفتاب سوخته داشت. چشم‌هایش با نگاهی حریص سگ را می پایید. همه اینها را خوب به یاد داشت. و اینکه بعد از آن پیچید توی یک کوچه خلوت و کنار چنار خشکی به دیوار تکیه داد. پوستش رفته بود و تنه‌اش پوک شده بود. با این حال با سماجت سر پا مانده بود. آتش سیگار که به انگشت‌هاش رسید و سوزاندش، باعث شد به خودش بیاید. هیچ جوری توی کتش نمی‌رفت. او پسر علیخان بود و البته وارثش و حالا همه کاره ملک اربابی. با قدم‌های تند راه برگشت را پیش گرفت. به خانه که رسید دسته بیل بلندی را برداشت و راهش را به طرف اتاقک ته باغ کج کرد.</p>

<p>سلیمان</p>

<p>آتش ذغال‌هایش توی ایوان از بالای کاهدان انبار معلوم بود. علیخان نشسته بود و مثل هر شب وافور را گذاشته بود گوشه لبش. گویی عزمش را جزم کرده بود قبل از مرگ هر چه را که داشت دود کند. بغل ملا نشسته بود و آتش ذغال را فوت می‌کرد. گر می‌گرفت و تا جرقه میزد، شروع می‌کرد به فحش دادن که: «قرمساق‌ها مگر نگفتم خوب بگردانیدش.» اهل نماز و روزه بود، ولی حتی ماه رمضان هم نوبت‌های تریاکش ترک نمی‌شد. مجوزش را هم از ملا گرفته بود. البته سهم ملا هم از همنشینی پای بساط می‌رسید، که هیچ وقت از تو نمی‌انداخت. حتی آن شب هم که همه رفته بودند دنبال گرگ‌ها. اول از همه او دیده بودشان، چشم‌هایشان را که برق می‌زد. تیر انداخته بود از همان بالای آغل که نشسته بود. خیالش آمده بود سگند، قبل از اینکه خوب نزدیک شوند. به همین خاطر تیرش نشسته بود روی پشت یکیشان. سگ‌ها هم معلوم نبود صدایشان چرا در نیامده بود. حالا هم که رفته بودند رد بزنند، سلیمان مانده بود بالای کاهدان بپا. صدا که از پایین آمد خشکش زد. فقط او می‌دانست چون خودش آنجا بود. اما بقیه می‌گفتند بعد از اینکه پدرش داده بودش به علیخان نفرین کرده بود و پدرش از سر همان نفرین مرده بود. سلیمان اما می‌دانست مریم کار را فقط به نفرین نسپرده بود. هر شب می‌دیدش که تا کنار جو می‌رفت و پاهایش را توی آب می‌گذاشت. اما امشب فرق می‌کرد و تا در انبار را باز نکرد، متوجه‌اش نشد. موهای بلندش از پشت روسری گلدار قرمزی که مثل دستمال بسته بود بالای سرش بیرون ریخته بود. یک طره مو بر پیشانی خیسش افتاده بود و روی شقیقه از کنار چشم راستش تا روی گوشش کشیده شده بود. چند تار مو هم بر گونه‌های تبدارش خزیده بود و افشان شده بود. چشم و ابروی مشکی داشت. در چشم‌های درشتش رگ‌های سرخ دویده بود، لبریز از شرری مفتون کننده. لب‌هایش گلگون نبود ولی او دوست داشت. صورتی رنگ پریده بود و شیارهایشان از خشکی نبود. یک جوری انگار انار رسیده که ترک می‌خورد. قطره‌ای عرق از زیر چانه بر گردن برهنه کنار رگ نزدیک گلویش لغزید. خرده‌های ریز کاه بر گودی استخوان‌های ترقوه‌اش برق می‌زد، زیر نور مرده مهتاب که به زحمت از شکاف‌های سقف بر بدنش می‌نشست. شال سفیدی روی دوشش انداخته بود و زیر پستان‌هایش گره زده بود. لباسش تر شده بود و نفس نفس که می‌زد پستان‌هایش بالا و پایین می‌شد، بی‌فاصله چسبیده به پیرهنی که دور کمر باریکش چین خورده بود. بر ساق و مچ سفیدش هیچ النگویی نبود. لبه پایین دامنش را بالا گرفته بود و پاهایش تا زانو پیدا بود. پاشنه پایش را بالا گرفته بود. ماهیچه‌های پشت ساق‌های باریک و ورزیده‌اش منقبض و منبسط می‌شد. بر کف خاکی انبار که قدم میزد پاشنه و گودی پشت زانویش پر و خالی می‌شد و انحنای روی رانش از زیر دامن بالا می‌آمد و برجسته می‌شد. نگاهی به دور و برش انداخت و آرام سلیمان را صدا زد. سلیمان نفسش را که حبس شده بود بیرون داد و از روی کاهدان پایین آمد. خیره به ذرات غبار که در ستون‌های نور ملایم اطراف او می‌چرخید نگاه کرد. دستهایش را گرفت گونه‌اش را بوسید و او را به بغل گرفت. کنار هم روی علف‌های تازه دراز کشیدند. نمی‌دانست چقدر از این‌ها را مریم برای خداداد گفته بود. توی بغل سلیمان که دراز کشیده بود، لختی اندام‌هاش رنگ پریده می‌نمود. به جز گونه‌ها و خیرگی چشم‌هاش که هراسی بی حیا ته آنها موج می‌زد. اصلا مگر هیچ کس غیر از سلیمان که این تصویر را تا آن لحظه مثل یک راز با خودش حمل کرده بود، می‌توانست این معجزه را تعریف کند. چشم‌های خداداد چقدر شبیه چشم‌های مریم بود، وقتی که پدرش را برای آخرین بار نگاه می‌کرد.</p>

<p>خداداد</p>

<p>مریم برایشان فقط چشم‌هایش را به ارث نگذاشته بود. اینها را حالا خداداد می‌دید که با سری شکافته به چشم‌های پسرش خیره شده یود. به خاطرش آمد نوه‌اش هم با آن لباس قرمز آستین پاره و کله کچلش همان‌طور خیره نگاهش کرده بود.</p>]]></content><author><name>محمد زینلی</name><email>m@zmim.ir</email></author><category term="داستان کوتاه" /><summary type="html"><![CDATA[یک داستان کوتاه قدیمی]]></summary></entry><entry xml:lang="en"><title type="html">Adding a Comments Section with Mastodon</title><link href="https://zmim.ir/en/mastodon-comments/" rel="alternate" type="text/html" title="Adding a Comments Section with Mastodon" /><published>2021-01-19T14:20:00+03:30</published><updated>2021-01-19T14:20:00+03:30</updated><id>https://zmim.ir/mastodon-comments</id><content type="html" xml:base="https://zmim.ir/mastodon-comments/"><![CDATA[<h1 id="mastodon">Mastodon</h1>

<p><a href="https://en.wikipedia.org/wiki/Mastodon_(social_network)" target="_blank" rel="noopener noreferrer">From Wikipedia, the free encyclopedia</a></p>

<p><a href="https://joinmastodon.org/" target="_blank" rel="noopener noreferrer">Mastodon</a> is a free and open-source self-hosted social networking service that allows anyone to host their own server node in the network, with user bases distributed across different servers.</p>

<p>These servers are interconnected as a federated social network, allowing their users to interact seamlessly with one another. Mastodon is part of a <a href="https://fediverse.party/" target="_blank" rel="noopener noreferrer">larger federation</a> that enables users to interact with other free platforms such as <a href="https://joinpeertube.org/" target="_blank" rel="noopener noreferrer">PeerTube</a> and <a href="https://friendi.ca/" target="_blank" rel="noopener noreferrer">Friendica</a> that support the same protocols.</p>

<h1 id="adding-a-comments-section-with-mastodon">Adding a Comments Section with Mastodon</h1>

<p>One of the challenges with static websites is adding a comments section. There are various options for doing this. One simple solution is Disqus. However, Disqus was never an appealing option for me because it is heavy, loads a lot of unnecessary scripts on pages, and most importantly is not free software. For more alternatives, you can see <a href="https://mehdix.ir/static-comments.html" target="_blank" rel="noopener noreferrer">this post</a> by Mehdi Sadeghi.</p>

<p>Thanks to <a href="https://linuxrocks.online/@carl" target="_blank" rel="noopener noreferrer">‪@carl‬</a> for writing the code and the report in <a href="https://carlschwan.eu/2020/12/29/adding-comments-to-your-static-blog-with-mastodon/" target="_blank" rel="noopener noreferrer">this post</a>, you can now send me your comments using a Mastodon account, and view what others have written.</p>

<h2 id="javascript-code">JavaScript Code</h2>

<p>Since the original code was written for <a href="https://gohugo.io/" target="_blank" rel="noopener noreferrer"><code class="language-plaintext highlighter-rouge">#HUGO</code></a>, I made minor modifications to make it compatible with <a href="https://jekyllrb.com/" target="_blank" rel="noopener noreferrer"><code class="language-plaintext highlighter-rouge">#JEKYLL</code></a> and placed it in the <a href="https://raw.githubusercontent.com/mhdzli/zmim.ir/master/src/_includes/mastodon.html" target="_blank" rel="noopener noreferrer"><code class="language-plaintext highlighter-rouge">mastodon.html</code></a> file inside the <a href="https://github.com/mhdzli/zmim.ir/tree/master/src/_includes" target="_blank" rel="noopener noreferrer"><code class="language-plaintext highlighter-rouge">_includes</code></a> folder:</p>

<div class="code-block">

<figure class="highlight"><pre><code class="language-html" data-lang="html"><span class="nt">&lt;div</span> <span class="na">class=</span><span class="s">"page-content"</span><span class="nt">&gt;</span>
  <span class="nt">&lt;h2&gt;</span>Comments<span class="nt">&lt;/h2&gt;</span>
  <span class="nt">&lt;p&gt;</span>You can view the comments for this post on Mastodon and <span class="nt">&lt;a</span> <span class="na">class=</span><span class="s">"link"</span> <span class="na">href=</span><span class="s">"https://{{ page.mastodon.host }}/@{{ page.mastodon.username }}/{{ page.mastodon.id }}"</span> <span class="na">target=</span><span class="s">"_blank"</span> <span class="na">rel=</span><span class="s">"noopener noreferrer"</span><span class="nt">&gt;</span> here<span class="nt">&lt;/a&gt;</span>. To write your own comment, click the link below.<span class="nt">&lt;/p&gt;</span>
  <span class="nt">&lt;p&gt;&lt;a</span> <span class="na">class=</span><span class="s">"button"</span> <span class="na">href=</span><span class="s">"https://{{ page.mastodon.host }}/@{{ page.mastodon.username }}/{{ page.mastodon.id }}"</span> <span class="na">target=</span><span class="s">"_blank"</span> <span class="na">rel=</span><span class="s">"noopener noreferrer"</span><span class="nt">&gt;</span>Write a comment<span class="nt">&lt;/a&gt;&lt;/p&gt;</span>
  <span class="nt">&lt;p&gt;&lt;/p&gt;</span>
  <span class="nt">&lt;p</span> <span class="na">id=</span><span class="s">"mastodon-comments-list"</span><span class="nt">&gt;&lt;a</span> <span class="na">id=</span><span class="s">"load-comment"</span> <span class="na">class=</span><span class="s">"button"</span><span class="nt">&gt;</span>View comments<span class="nt">&lt;/a&gt;&lt;/p&gt;</span>
  <span class="nt">&lt;div</span> <span class="na">id=</span><span class="s">"comments-wrapper"</span><span class="nt">&gt;</span>
    <span class="nt">&lt;noscript&gt;&lt;p&gt;</span>JavaScript must be enabled for this section, or view the <span class="nt">&lt;a</span> <span class="na">href=</span><span class="s">"https://{{ page.mastodon.host }}/@{{ page.mastodon.username }}/{{ page.mastodon.id }}"</span> <span class="na">target=</span><span class="s">"_blank"</span> <span class="na">rel=</span><span class="s">"noopener noreferrer"</span><span class="nt">&gt;</span>original post<span class="nt">&lt;/a&gt;</span> on Mastodon<span class="nt">&lt;/p&gt;&lt;/noscript&gt;</span>
  <span class="nt">&lt;/div&gt;</span>
  <span class="nt">&lt;script </span><span class="na">src=</span><span class="s">"/assets/js/purify.min.js"</span><span class="nt">&gt;&lt;/script&gt;</span>
  <span class="nt">&lt;script </span><span class="na">type=</span><span class="s">"text/javascript"</span><span class="nt">&gt;</span>
    <span class="kd">function</span> <span class="nf">escapeHtml</span><span class="p">(</span><span class="nx">unsafe</span><span class="p">)</span> <span class="p">{</span>
      <span class="k">return</span> <span class="nx">unsafe</span>
        <span class="p">.</span><span class="nf">replace</span><span class="p">(</span><span class="sr">/&amp;/g</span><span class="p">,</span> <span class="dl">"</span><span class="s2">&amp;amp;</span><span class="dl">"</span><span class="p">)</span>
        <span class="p">.</span><span class="nx">replace</span><span class="p">(</span><span class="sr">/&lt;/g</span><span class="p">,</span> <span class="dl">"</span><span class="s2">&amp;lt;</span><span class="dl">"</span><span class="p">)</span>
        <span class="p">.</span><span class="nf">replace</span><span class="p">(</span><span class="sr">/&gt;/g</span><span class="p">,</span> <span class="dl">"</span><span class="s2">&amp;gt;</span><span class="dl">"</span><span class="p">)</span>
        <span class="p">.</span><span class="nf">replace</span><span class="p">(</span><span class="sr">/"/g</span><span class="p">,</span> <span class="dl">"</span><span class="s2">&amp;quot;</span><span class="dl">"</span><span class="p">)</span>
        <span class="p">.</span><span class="nf">replace</span><span class="p">(</span><span class="sr">/'/g</span><span class="p">,</span> <span class="dl">"</span><span class="s2">&amp;#039;</span><span class="dl">"</span><span class="p">);</span>
    <span class="p">}</span>

    <span class="kd">function</span> <span class="nf">emojify</span><span class="p">(</span><span class="nx">input</span><span class="p">,</span> <span class="nx">emojis</span><span class="p">)</span> <span class="p">{</span>
      <span class="kd">let</span> <span class="nx">output</span> <span class="o">=</span> <span class="nx">input</span><span class="p">;</span>

      <span class="nx">emojis</span><span class="p">.</span><span class="nf">forEach</span><span class="p">(</span><span class="nx">emoji</span> <span class="o">=&gt;</span> <span class="p">{</span>
        <span class="kd">let</span> <span class="nx">picture</span> <span class="o">=</span> <span class="nb">document</span><span class="p">.</span><span class="nf">createElement</span><span class="p">(</span><span class="dl">"</span><span class="s2">picture</span><span class="dl">"</span><span class="p">);</span>

        <span class="kd">let</span> <span class="nx">source</span> <span class="o">=</span> <span class="nb">document</span><span class="p">.</span><span class="nf">createElement</span><span class="p">(</span><span class="dl">"</span><span class="s2">source</span><span class="dl">"</span><span class="p">);</span>
        <span class="nx">source</span><span class="p">.</span><span class="nf">setAttribute</span><span class="p">(</span><span class="dl">"</span><span class="s2">srcset</span><span class="dl">"</span><span class="p">,</span> <span class="nf">escapeHtml</span><span class="p">(</span><span class="nx">emoji</span><span class="p">.</span><span class="nx">url</span><span class="p">));</span>
        <span class="nx">source</span><span class="p">.</span><span class="nf">setAttribute</span><span class="p">(</span><span class="dl">"</span><span class="s2">media</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">(prefers-reduced-motion: no-preference)</span><span class="dl">"</span><span class="p">);</span>

        <span class="kd">let</span> <span class="nx">img</span> <span class="o">=</span> <span class="nb">document</span><span class="p">.</span><span class="nf">createElement</span><span class="p">(</span><span class="dl">"</span><span class="s2">img</span><span class="dl">"</span><span class="p">);</span>
        <span class="nx">img</span><span class="p">.</span><span class="nx">className</span> <span class="o">=</span> <span class="dl">"</span><span class="s2">emoji</span><span class="dl">"</span><span class="p">;</span>
        <span class="nx">img</span><span class="p">.</span><span class="nf">setAttribute</span><span class="p">(</span><span class="dl">"</span><span class="s2">src</span><span class="dl">"</span><span class="p">,</span> <span class="nf">escapeHtml</span><span class="p">(</span><span class="nx">emoji</span><span class="p">.</span><span class="nx">static_url</span><span class="p">));</span>
        <span class="nx">img</span><span class="p">.</span><span class="nf">setAttribute</span><span class="p">(</span><span class="dl">"</span><span class="s2">alt</span><span class="dl">"</span><span class="p">,</span> <span class="s2">`:</span><span class="p">${</span> <span class="nx">emoji</span><span class="p">.</span><span class="nx">shortcode</span> <span class="p">}</span><span class="s2">:`</span><span class="p">);</span>
        <span class="nx">img</span><span class="p">.</span><span class="nf">setAttribute</span><span class="p">(</span><span class="dl">"</span><span class="s2">title</span><span class="dl">"</span><span class="p">,</span> <span class="s2">`:</span><span class="p">${</span> <span class="nx">emoji</span><span class="p">.</span><span class="nx">shortcode</span> <span class="p">}</span><span class="s2">:`</span><span class="p">);</span>
        <span class="nx">img</span><span class="p">.</span><span class="nf">setAttribute</span><span class="p">(</span><span class="dl">"</span><span class="s2">width</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">20</span><span class="dl">"</span><span class="p">);</span>
        <span class="nx">img</span><span class="p">.</span><span class="nf">setAttribute</span><span class="p">(</span><span class="dl">"</span><span class="s2">height</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">20</span><span class="dl">"</span><span class="p">);</span>

        <span class="nx">picture</span><span class="p">.</span><span class="nf">appendChild</span><span class="p">(</span><span class="nx">source</span><span class="p">);</span>
        <span class="nx">picture</span><span class="p">.</span><span class="nf">appendChild</span><span class="p">(</span><span class="nx">img</span><span class="p">);</span>

        <span class="nx">output</span> <span class="o">=</span> <span class="nx">output</span><span class="p">.</span><span class="nf">replace</span><span class="p">(</span><span class="s2">`:</span><span class="p">${</span> <span class="nx">emoji</span><span class="p">.</span><span class="nx">shortcode</span> <span class="p">}</span><span class="s2">:`</span><span class="p">,</span> <span class="nx">picture</span><span class="p">.</span><span class="nx">outerHTML</span><span class="p">);</span>
      <span class="p">});</span>

      <span class="k">return</span> <span class="nx">output</span><span class="p">;</span>
    <span class="p">}</span>

    <span class="kd">function</span> <span class="nf">loadcomments</span><span class="p">()</span> <span class="p">{</span>
      <span class="nb">document</span><span class="p">.</span><span class="nf">getElementById</span><span class="p">(</span><span class="dl">"</span><span class="s2">load-comment</span><span class="dl">"</span><span class="p">).</span><span class="nx">innerHTML</span> <span class="o">=</span> <span class="dl">"</span><span class="s2">Loading comments</span><span class="dl">"</span><span class="p">;</span>
      <span class="nf">fetch</span><span class="p">(</span><span class="dl">'</span><span class="s1">https://{{ page.mastodon.host }}/api/v1/statuses/{{ page.mastodon.id }}/context</span><span class="dl">'</span><span class="p">)</span>
        <span class="p">.</span><span class="nf">then</span><span class="p">(</span><span class="kd">function</span><span class="p">(</span><span class="nx">response</span><span class="p">)</span> <span class="p">{</span>
          <span class="k">return</span> <span class="nx">response</span><span class="p">.</span><span class="nf">json</span><span class="p">();</span>
        <span class="p">})</span>
        <span class="p">.</span><span class="nf">then</span><span class="p">(</span><span class="kd">function</span><span class="p">(</span><span class="nx">data</span><span class="p">)</span> <span class="p">{</span>
          <span class="kd">let</span> <span class="nx">descendants</span> <span class="o">=</span> <span class="nx">data</span><span class="p">[</span><span class="dl">'</span><span class="s1">descendants</span><span class="dl">'</span><span class="p">];</span>
          <span class="k">if</span><span class="p">(</span>
            <span class="nx">descendants</span> <span class="o">&amp;&amp;</span>
            <span class="nb">Array</span><span class="p">.</span><span class="nf">isArray</span><span class="p">(</span><span class="nx">descendants</span><span class="p">)</span> <span class="o">&amp;&amp;</span>
            <span class="nx">descendants</span><span class="p">.</span><span class="nx">length</span> <span class="o">&gt;</span> <span class="mi">0</span>
          <span class="p">)</span> <span class="p">{</span>
            <span class="nb">document</span><span class="p">.</span><span class="nf">getElementById</span><span class="p">(</span><span class="dl">'</span><span class="s1">mastodon-comments-list</span><span class="dl">'</span><span class="p">).</span><span class="nx">innerHTML</span> <span class="o">=</span> <span class="dl">""</span><span class="p">;</span>
            <span class="nx">descendants</span><span class="p">.</span><span class="nf">forEach</span><span class="p">(</span><span class="kd">function</span><span class="p">(</span><span class="nx">reply</span><span class="p">)</span> <span class="p">{</span>
              <span class="k">if</span><span class="p">(</span> <span class="nx">reply</span><span class="p">.</span><span class="nx">account</span><span class="p">.</span><span class="nx">display_name</span><span class="p">.</span><span class="nx">length</span> <span class="o">&gt;</span> <span class="mi">0</span> <span class="p">)</span> <span class="p">{</span>
                <span class="nx">reply</span><span class="p">.</span><span class="nx">account</span><span class="p">.</span><span class="nx">display_name</span> <span class="o">=</span> <span class="nf">escapeHtml</span><span class="p">(</span><span class="nx">reply</span><span class="p">.</span><span class="nx">account</span><span class="p">.</span><span class="nx">display_name</span><span class="p">);</span>
                <span class="nx">reply</span><span class="p">.</span><span class="nx">account</span><span class="p">.</span><span class="nx">display_name</span> <span class="o">=</span> <span class="nf">emojify</span><span class="p">(</span><span class="nx">reply</span><span class="p">.</span><span class="nx">account</span><span class="p">.</span><span class="nx">display_name</span><span class="p">,</span> <span class="nx">reply</span><span class="p">.</span><span class="nx">account</span><span class="p">.</span><span class="nx">emojis</span><span class="p">);</span>
              <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
                <span class="nx">reply</span><span class="p">.</span><span class="nx">account</span><span class="p">.</span><span class="nx">display_name</span> <span class="o">=</span> <span class="nx">reply</span><span class="p">.</span><span class="nx">account</span><span class="p">.</span><span class="nx">username</span><span class="p">;</span>
              <span class="p">};</span>

              <span class="nx">reply</span><span class="p">.</span><span class="nx">content</span> <span class="o">=</span> <span class="nf">emojify</span><span class="p">(</span><span class="nx">reply</span><span class="p">.</span><span class="nx">content</span><span class="p">,</span> <span class="nx">reply</span><span class="p">.</span><span class="nx">emojis</span><span class="p">);</span>

              <span class="nx">reply</span><span class="p">.</span><span class="nx">created_at</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">Date</span><span class="p">(</span> <span class="nx">reply</span><span class="p">.</span><span class="nx">created_at</span> <span class="p">).</span><span class="nf">toLocaleString</span><span class="p">(</span><span class="dl">'</span><span class="s1">en-US</span><span class="dl">'</span><span class="p">,</span> <span class="p">{</span>
                <span class="na">dateStyle</span><span class="p">:</span> <span class="dl">"</span><span class="s2">long</span><span class="dl">"</span><span class="p">,</span>
                <span class="na">timeStyle</span><span class="p">:</span> <span class="dl">"</span><span class="s2">short</span><span class="dl">"</span><span class="p">,</span>
              <span class="p">});</span>

              <span class="kd">let</span> <span class="nx">instance</span> <span class="o">=</span> <span class="dl">""</span><span class="p">;</span>
              <span class="k">if</span><span class="p">(</span> <span class="nx">reply</span><span class="p">.</span><span class="nx">account</span><span class="p">.</span><span class="nx">acct</span><span class="p">.</span><span class="nf">includes</span><span class="p">(</span><span class="dl">"</span><span class="s2">@</span><span class="dl">"</span><span class="p">)</span> <span class="p">)</span> <span class="p">{</span>
                <span class="nx">instance</span> <span class="o">=</span> <span class="nx">reply</span><span class="p">.</span><span class="nx">account</span><span class="p">.</span><span class="nx">acct</span><span class="p">.</span><span class="nf">split</span><span class="p">(</span><span class="dl">"</span><span class="s2">@</span><span class="dl">"</span><span class="p">)[</span><span class="mi">1</span><span class="p">];</span>
              <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
                <span class="nx">instance</span> <span class="o">=</span> <span class="dl">"</span><span class="s2">{{ page.mastodon.host }}</span><span class="dl">"</span><span class="p">;</span>
              <span class="p">}</span>

              <span class="nx">mastodonComment</span> <span class="o">=</span>
                <span class="s2">`&lt;div class="mastodon-comment"&gt;
                &lt;div class="avatar"&gt;
                &lt;img src="</span><span class="p">${</span><span class="nf">escapeHtml</span><span class="p">(</span><span class="nx">reply</span><span class="p">.</span><span class="nx">account</span><span class="p">.</span><span class="nx">avatar_static</span><span class="p">)}</span><span class="s2">" height=60 width=60 alt=""&gt;
                &lt;/div&gt;
                &lt;div class="content"&gt;
                &lt;div class="author" title="profile: @</span><span class="p">${</span> <span class="nx">reply</span><span class="p">.</span><span class="nx">account</span><span class="p">.</span><span class="nx">username</span> <span class="p">}</span><span class="s2">@</span><span class="p">${</span> <span class="nx">instance</span> <span class="p">}</span><span class="s2">"&gt;
                &lt;a href="</span><span class="p">${</span><span class="nx">reply</span><span class="p">.</span><span class="nx">account</span><span class="p">.</span><span class="nx">url</span><span class="p">}</span><span class="s2">" target="_blank" rel="external nofollow"&gt;
                &lt;span&gt;</span><span class="p">${</span><span class="nx">reply</span><span class="p">.</span><span class="nx">account</span><span class="p">.</span><span class="nx">display_name</span><span class="p">}</span><span class="s2">&lt;/span&gt;
                &lt;span class="instance"&gt;</span><span class="p">${</span><span class="nx">instance</span><span class="p">}</span><span class="s2">&lt;/span&gt;
                &lt;/a&gt;
                &lt;/div&gt;
                &lt;div title="View comment on </span><span class="p">${</span> <span class="nx">instance</span> <span class="p">}</span><span class="s2">"&gt;
                &lt;a class="date" href="</span><span class="p">${</span><span class="nx">reply</span><span class="p">.</span><span class="nx">uri</span><span class="p">}</span><span class="s2">" target="_blank" rel="external nofollow"&gt;
                </span><span class="p">${</span><span class="nx">reply</span><span class="p">.</span><span class="nx">created_at</span><span class="p">}</span><span class="s2">
                &lt;/a&gt;
                &lt;/div&gt;
                &lt;div class="mastodon-comment-content"&gt;</span><span class="p">${</span><span class="nx">reply</span><span class="p">.</span><span class="nx">content</span><span class="p">}</span><span class="s2">&lt;/div&gt;
                &lt;/div&gt;
                &lt;/div&gt;`</span><span class="p">;</span>
              <span class="nb">document</span><span class="p">.</span><span class="nf">getElementById</span><span class="p">(</span><span class="dl">'</span><span class="s1">mastodon-comments-list</span><span class="dl">'</span><span class="p">).</span><span class="nf">appendChild</span><span class="p">(</span><span class="nx">DOMPurify</span><span class="p">.</span><span class="nf">sanitize</span><span class="p">(</span><span class="nx">mastodonComment</span><span class="p">,</span> <span class="p">{</span><span class="dl">'</span><span class="s1">RETURN_DOM_FRAGMENT</span><span class="dl">'</span><span class="p">:</span> <span class="kc">true</span><span class="p">}));</span>
            <span class="p">});</span>
          <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
            <span class="nb">document</span><span class="p">.</span><span class="nf">getElementById</span><span class="p">(</span><span class="dl">'</span><span class="s1">mastodon-comments-list</span><span class="dl">'</span><span class="p">).</span><span class="nx">innerHTML</span> <span class="o">=</span> <span class="dl">"</span><span class="s2">&lt;p&gt;No comments yet&lt;/p&gt;</span><span class="dl">"</span><span class="p">;</span>
          <span class="p">}</span>
        <span class="p">});</span>
    <span class="p">}</span>
    <span class="nb">document</span><span class="p">.</span><span class="nf">getElementById</span><span class="p">(</span><span class="dl">"</span><span class="s2">load-comment</span><span class="dl">"</span><span class="p">).</span><span class="nf">addEventListener</span><span class="p">(</span><span class="dl">"</span><span class="s2">click</span><span class="dl">"</span><span class="p">,</span> <span class="nx">loadcomments</span><span class="p">);</span>
  <span class="nt">&lt;/script&gt;</span>
<span class="nt">&lt;/div&gt;</span></code></pre></figure>

</div>

<p>Then in the <a href="https://raw.githubusercontent.com/mhdzli/zmim.ir/master/src/_layouts/post.html" target="_blank" rel="noopener noreferrer">post layout</a>, I added a condition so that whenever <code class="language-plaintext highlighter-rouge">mastodon</code> is present in the post’s <a href="https://jekyllrb.com/docs/front-matter/" target="_blank" rel="noopener noreferrer"><code class="language-plaintext highlighter-rouge">frontmatter</code></a>, the comments section is included:</p>

<div class="code-block">

<figure class="highlight"><pre><code class="language-liquid" data-lang="liquid"><span class="cp">{%</span><span class="w"> </span><span class="nt">if</span><span class="w"> </span><span class="nv">page</span><span class="p">.</span><span class="nv">mastodon</span><span class="w"> </span><span class="cp">%}</span>
  <span class="cp">{%</span><span class="w"> </span><span class="nt">include</span><span class="w"> </span>mastodon.html<span class="w"> </span><span class="cp">%}</span>
<span class="cp">{%</span><span class="w"> </span><span class="nt">endif</span><span class="w"> </span><span class="cp">%}</span></code></pre></figure>

</div>

<p>And in the <code class="language-plaintext highlighter-rouge">frontmatter</code>, <code class="language-plaintext highlighter-rouge">mastodon</code> should be added with this structure:</p>

<div class="code-block">

<figure class="highlight"><pre><code class="language-yaml" data-lang="yaml"><span class="na">mastodon</span><span class="pi">:</span>
  <span class="na">host</span><span class="pi">:</span> <span class="s">mas.to</span>
  <span class="na">username</span><span class="pi">:</span> <span class="s">mz</span>
  <span class="na">id</span><span class="pi">:</span> <span class="s">105582586560918183</span></code></pre></figure>

</div>

<p>Where the following must be specified:</p>

<ul>
  <li>host: the Mastodon server address</li>
  <li>username: the Mastodon username</li>
  <li>id: the unique ID of the toot related to the post</li>
</ul>

<p>Finally, I used <a href="https://github.com/mhdzli/zmim.ir/commit/78e351e5f809ead9eb4a77021026c5364c6e2081" target="_blank" rel="noopener noreferrer">these few lines of <code class="language-plaintext highlighter-rouge">CSS</code></a> for better comment display.</p>

<p>You can try the final result here:</p>]]></content><author><name>محمد زینلی</name><email>m@zmim.ir</email></author><category term="foss" /><category term="mastodon" /><category term="fediverse" /><summary type="html"><![CDATA[Adding a comments section using Mastodon (a free and decentralized social network).]]></summary></entry><entry xml:lang="en"><title type="html">Noise Reduction with SoX</title><link href="https://zmim.ir/en/sox/" rel="alternate" type="text/html" title="Noise Reduction with SoX" /><published>2020-12-28T12:20:00+03:30</published><updated>2020-12-28T12:20:00+03:30</updated><id>https://zmim.ir/sox</id><content type="html" xml:base="https://zmim.ir/sox/"><![CDATA[<h1 id="sox">SoX</h1>

<p><a href="http://sox.sourceforge.net/" target="_blank" rel="noopener noreferrer"><code class="language-plaintext highlighter-rouge">SoX</code></a> is a tool for editing audio files, available on GNU/Linux, macOS, and Windows. You can also use it on Android inside the <a href="https://termux.com/" target="_blank" rel="noopener noreferrer"><code class="language-plaintext highlighter-rouge">termux</code></a> environment.</p>

<p>In this post, we will first use <code class="language-plaintext highlighter-rouge">SoX</code> to reduce noise in an audio file, and then examine a script for noise reduction in audio and video files using <code class="language-plaintext highlighter-rouge">SoX</code> and <code class="language-plaintext highlighter-rouge">FFmpeg</code>.</p>

<h1 id="noise-reduction-with-sox">Noise Reduction with <code class="language-plaintext highlighter-rouge">SoX</code></h1>

<p>If we have an audio file called <code class="language-plaintext highlighter-rouge">audio.wav</code> that has a small section of silence — or more accurately, ambient noise — recorded at the beginning, we can extract that section using <code class="language-plaintext highlighter-rouge">SoX</code>:</p>

<div class="code-block">

<figure class="highlight"><pre><code class="language-bash" data-lang="bash"><span class="c"># sox in.ext out.ext trim {start: s.ms} {duration: s.ms}</span>
sox audio.wav noise.wav trim 0 0.900</code></pre></figure>

</div>

<p>Here, <code class="language-plaintext highlighter-rouge">SoX</code> takes <code class="language-plaintext highlighter-rouge">audio.wav</code> as input, extracts the first <code class="language-plaintext highlighter-rouge">0.9</code> seconds of it, and saves it as <code class="language-plaintext highlighter-rouge">noise.wav</code>.</p>

<p>Now we use this section to create a noise profile file called <code class="language-plaintext highlighter-rouge">noise.prof</code>:</p>

<div class="code-block">

<figure class="highlight"><pre><code class="language-bash" data-lang="bash">sox noise.wav <span class="nt">-n</span> noiseprof noise.prof</code></pre></figure>

</div>

<p>Finally, we perform noise reduction based on the generated profile. A sensitivity factor is used to control the amount of noise reduction; according to <a href="http://www.zoharbabin.com/how-to-do-noise-reduction-using-ffmpeg-and-sox/" target="_blank" rel="noopener noreferrer">this source</a>, a value between <code class="language-plaintext highlighter-rouge">0.2</code> and <code class="language-plaintext highlighter-rouge">0.3</code> produces the best results:</p>

<div class="code-block">

<figure class="highlight"><pre><code class="language-bash" data-lang="bash">sox audio.wav audio-clean.wav noisered noise.prof 0.21</code></pre></figure>

</div>

<h1 id="a-script-for-noise-reduction-in-audio-and-video-files-with-ffmpeg-and-sox">A Script for Noise Reduction in Audio and Video Files with <code class="language-plaintext highlighter-rouge">FFmpeg</code> and <code class="language-plaintext highlighter-rouge">SoX</code></h1>

<p>This script takes two paths — first the input file, then the output file (<code class="language-plaintext highlighter-rouge">$1</code> and <code class="language-plaintext highlighter-rouge">$2</code>). If the input file is a video, it uses <code class="language-plaintext highlighter-rouge">FFmpeg</code> to extract the audio. It then reduces noise in the audio file as described above, and finally, if the input was a video, uses <code class="language-plaintext highlighter-rouge">FFmpeg</code> again to merge the audio and video back together.</p>

<div class="code-block">

<figure class="highlight"><pre><code class="language-bash" data-lang="bash"><span class="c">#!/usr/bin/sh</span>

usage <span class="o">()</span>
<span class="o">{</span>
    <span class="nb">printf</span> <span class="s2">"Usage : noisereduce &lt;input video file&gt; &lt;output video file&gt;</span><span class="se">\n</span><span class="s2">"</span>
    <span class="nb">exit</span>
<span class="o">}</span>

<span class="c"># Tests for requirements</span>
ffmpeg <span class="nt">-version</span> <span class="o">&gt;</span>/dev/null <span class="o">||</span> <span class="o">{</span> <span class="nb">echo</span> <span class="o">&gt;</span>&amp;2 <span class="s2">"We require 'ffmpeg' but it's not installed. Install it by 'sudo apt-get install ffmpeg' Aborting."</span><span class="p">;</span> <span class="nb">exit </span>1<span class="p">;</span> <span class="o">}</span>
sox <span class="nt">--version</span> <span class="o">&gt;</span>/dev/null <span class="o">||</span> <span class="o">{</span> <span class="nb">echo</span> <span class="o">&gt;</span>&amp;2 <span class="s2">"We require 'sox' but it's not installed. Install it by 'sudo apt-get install sox' Aborting."</span><span class="p">;</span> <span class="nb">exit </span>1<span class="p">;</span> <span class="o">}</span>

<span class="k">if</span> <span class="o">[</span> <span class="s2">"$#"</span> <span class="nt">-ne</span> 2 <span class="o">]</span>
<span class="k">then
  </span>usage
<span class="k">fi

if</span> <span class="o">[</span> <span class="o">!</span> <span class="nt">-e</span> <span class="s2">"</span><span class="nv">$1</span><span class="s2">"</span> <span class="o">]</span>
<span class="k">then
    </span><span class="nb">printf</span> <span class="s2">"File not found: %s</span><span class="se">\n</span><span class="s2">"</span> <span class="s2">"</span><span class="nv">$1</span><span class="s2">"</span>
    <span class="nb">exit
</span><span class="k">fi

if</span> <span class="o">[</span> <span class="nt">-e</span> <span class="s2">"</span><span class="nv">$2</span><span class="s2">"</span> <span class="o">]</span>
<span class="k">then
    </span><span class="nb">printf</span> <span class="s2">"File %s already exists, overwrite? [y/N]</span><span class="se">\n</span><span class="s2">: "</span> <span class="s2">"</span><span class="nv">$2</span><span class="s2">"</span>
    <span class="nb">read</span> <span class="nt">-r</span> yn
    <span class="k">case</span> <span class="nv">$yn</span> <span class="k">in</span>
        <span class="o">[</span>Yy]<span class="k">*</span> <span class="p">)</span> <span class="p">;;</span>
        <span class="k">*</span> <span class="p">)</span> <span class="nb">exit</span><span class="p">;;</span>
    <span class="k">esac</span>
<span class="k">fi

</span><span class="nv">inBasename</span><span class="o">=</span><span class="si">$(</span><span class="nb">basename</span> <span class="s2">"</span><span class="nv">$1</span><span class="s2">"</span><span class="si">)</span>
<span class="nv">inExt</span><span class="o">=</span><span class="s2">"</span><span class="k">${</span><span class="nv">inBasename</span><span class="p">##*.</span><span class="k">}</span><span class="s2">"</span>

<span class="nv">isVideoStr</span><span class="o">=</span><span class="si">$(</span>ffprobe <span class="nt">-v</span> warning <span class="nt">-show_streams</span> <span class="s2">"</span><span class="nv">$1</span><span class="s2">"</span> | <span class="nb">grep </span><span class="nv">codec_type</span><span class="o">=</span>video<span class="si">)</span>
<span class="k">if</span> <span class="o">[</span> <span class="nt">-n</span> <span class="s2">"</span><span class="nv">$isVideoStr</span><span class="s2">"</span> <span class="o">]</span>
<span class="k">then
    </span><span class="nv">isVideo</span><span class="o">=</span>1
    <span class="nb">printf</span> <span class="s2">"Detected %s as a video file</span><span class="se">\n</span><span class="s2">"</span> <span class="s2">"</span><span class="nv">$inBasename</span><span class="s2">"</span>
<span class="k">else
    </span><span class="nv">isVideo</span><span class="o">=</span>0
    <span class="nb">printf</span> <span class="s2">"Detected %s as an audio file</span><span class="se">\n</span><span class="s2">"</span> <span class="s2">"</span><span class="nv">$inBasename</span><span class="s2">"</span>
<span class="k">fi

</span><span class="nb">printf</span> <span class="s2">"Sample noise start time [00:00:00]: "</span>
<span class="nb">read</span> <span class="nt">-r</span> sampleStart
<span class="k">if</span> <span class="o">[</span> <span class="nt">-z</span> <span class="s2">"</span><span class="nv">$sampleStart</span><span class="s2">"</span> <span class="o">]</span> <span class="p">;</span> <span class="k">then </span><span class="nv">sampleStart</span><span class="o">=</span><span class="s2">"00:00:00"</span><span class="p">;</span> <span class="k">fi
</span><span class="nb">printf</span> <span class="s2">"Sample noise end time [00:00:00.900]: "</span>
<span class="nb">read</span> <span class="nt">-r</span> sampleEnd
<span class="k">if</span> <span class="o">[</span> <span class="nt">-z</span> <span class="s2">"</span><span class="nv">$sampleEnd</span><span class="s2">"</span> <span class="o">]</span> <span class="p">;</span> <span class="k">then </span><span class="nv">sampleEnd</span><span class="o">=</span><span class="s2">"00:00:00.900"</span><span class="p">;</span> <span class="k">fi
</span><span class="nb">printf</span> <span class="s2">"Noise reduction amount [0.21]: "</span>
<span class="nb">read</span> <span class="nt">-r</span> sensitivity
<span class="k">if</span> <span class="o">[</span> <span class="nt">-z</span> <span class="s2">"</span><span class="nv">$sensitivity</span><span class="s2">"</span> <span class="o">]</span> <span class="p">;</span> <span class="k">then </span><span class="nv">sensitivity</span><span class="o">=</span><span class="s2">"0.21"</span><span class="p">;</span> <span class="k">fi


</span><span class="nv">tmpVidFile</span><span class="o">=</span><span class="s2">"/tmp/noiseclean_tmpvid.</span><span class="nv">$inExt</span><span class="s2">"</span>
<span class="nv">tmpAudFile</span><span class="o">=</span><span class="s2">"/tmp/noiseclean_tmpaud.wav"</span>
<span class="nv">noiseAudFile</span><span class="o">=</span><span class="s2">"/tmp/noiseclean_noiseaud.wav"</span>
<span class="nv">noiseProfFile</span><span class="o">=</span><span class="s2">"/tmp/noiseclean_noise.prof"</span>
<span class="nv">tmpAudCleanFile</span><span class="o">=</span><span class="s2">"/tmp/noiseclean_tmpaud-clean.wav"</span>

<span class="nb">printf</span> <span class="s2">"Cleaning noise on %s...</span><span class="se">\n</span><span class="s2">"</span> <span class="s2">"</span><span class="nv">$1</span><span class="s2">"</span>

<span class="k">if</span> <span class="o">[</span> <span class="nv">$isVideo</span> <span class="nt">-eq</span> <span class="s2">"1"</span> <span class="o">]</span><span class="p">;</span> <span class="k">then
    </span>ffmpeg <span class="nt">-v</span> warning <span class="nt">-y</span> <span class="nt">-i</span> <span class="s2">"</span><span class="nv">$1</span><span class="s2">"</span> <span class="nt">-qscale</span>:v 0 <span class="nt">-vcodec</span> copy <span class="nt">-an</span> <span class="s2">"</span><span class="nv">$tmpVidFile</span><span class="s2">"</span>
    ffmpeg <span class="nt">-v</span> warning <span class="nt">-y</span> <span class="nt">-i</span> <span class="s2">"</span><span class="nv">$1</span><span class="s2">"</span> <span class="nt">-qscale</span>:a 0 <span class="s2">"</span><span class="nv">$tmpAudFile</span><span class="s2">"</span>
<span class="k">else
    </span><span class="nb">cp</span> <span class="s2">"</span><span class="nv">$1</span><span class="s2">"</span> <span class="s2">"</span><span class="nv">$tmpAudFile</span><span class="s2">"</span>
<span class="k">fi
</span>ffmpeg <span class="nt">-v</span> warning <span class="nt">-y</span> <span class="nt">-i</span> <span class="s2">"</span><span class="nv">$1</span><span class="s2">"</span> <span class="nt">-vn</span> <span class="nt">-ss</span> <span class="s2">"</span><span class="nv">$sampleStart</span><span class="s2">"</span> <span class="nt">-t</span> <span class="s2">"</span><span class="nv">$sampleEnd</span><span class="s2">"</span> <span class="s2">"</span><span class="nv">$noiseAudFile</span><span class="s2">"</span>
sox <span class="s2">"</span><span class="nv">$noiseAudFile</span><span class="s2">"</span> <span class="nt">-n</span> noiseprof <span class="s2">"</span><span class="nv">$noiseProfFile</span><span class="s2">"</span>
sox <span class="s2">"</span><span class="nv">$tmpAudFile</span><span class="s2">"</span> <span class="s2">"</span><span class="nv">$tmpAudCleanFile</span><span class="s2">"</span> noisered <span class="s2">"</span><span class="nv">$noiseProfFile</span><span class="s2">"</span> <span class="s2">"</span><span class="nv">$sensitivity</span><span class="s2">"</span>
<span class="k">if</span> <span class="o">[</span> <span class="nv">$isVideo</span> <span class="nt">-eq</span> <span class="s2">"1"</span> <span class="o">]</span><span class="p">;</span> <span class="k">then
    </span>ffmpeg <span class="nt">-v</span> warning <span class="nt">-y</span> <span class="nt">-i</span> <span class="s2">"</span><span class="nv">$tmpAudCleanFile</span><span class="s2">"</span> <span class="nt">-i</span> <span class="s2">"</span><span class="nv">$tmpVidFile</span><span class="s2">"</span> <span class="nt">-vcodec</span> copy <span class="nt">-qscale</span>:v 0 <span class="nt">-qscale</span>:a 0 <span class="s2">"</span><span class="nv">$2</span><span class="s2">"</span>
<span class="k">else
    </span><span class="nb">cp</span> <span class="s2">"</span><span class="nv">$tmpAudCleanFile</span><span class="s2">"</span> <span class="s2">"</span><span class="nv">$2</span><span class="s2">"</span>
<span class="k">fi

</span><span class="nb">printf</span> <span class="s2">"Done"</span></code></pre></figure>

</div>

<h1 id="more-links">More Links</h1>

<ul>
  <li><a href="https://sourceforge.net/p/sox/code/ci/master/tree/scripts/" target="_blank" rel="noopener noreferrer">A few examples in the main repository</a></li>
  <li><a href="https://github.com/madskjeldgaard/sox-tricks" target="_blank" rel="noopener noreferrer">SoX tricks</a></li>
  <li><a href="https://github.com/HiSunzhenliang/SoX" target="_blank" rel="noopener noreferrer">A fork on GitHub</a></li>
  <li><a href="http://sox.sourceforge.net/Docs/Features" target="_blank" rel="noopener noreferrer">Supported features and formats</a></li>
  <li><a href="https://github.com/daizyu/sox-normalize-noise-reduction" target="_blank" rel="noopener noreferrer">A C# example</a></li>
</ul>

<p>If you prefer watching a tutorial video or want to hear the difference between the input and output:</p>

<div class="video">
  <iframe sandbox="allow-same-origin allow-scripts allow-popups" src="https://peertube.linuxrocks.online/videos/embed/de166e78-c3c8-440f-8557-bea6e26d1f9f" frameborder="0" allowfullscreen=""></iframe>
</div>]]></content><author><name>محمد زینلی</name><email>m@zmim.ir</email></author><category term="unix/linux" /><category term="CLI" /><summary type="html"><![CDATA[SoX (Swiss Army knife of sound processing) — a tool for audio editing]]></summary></entry><entry xml:lang="en"><title type="html">Mahour Extension Update</title><link href="https://zmim.ir/en/mahoor-update/" rel="alternate" type="text/html" title="Mahour Extension Update" /><published>2020-11-03T08:10:47+03:30</published><updated>2020-11-03T08:10:47+03:30</updated><id>https://zmim.ir/mahoor-update</id><content type="html" xml:base="https://zmim.ir/mahoor-update/"><![CDATA[<p>In this post, using the update of the <a href="https://addons.thunderbird.net/en-us/thunderbird/addon/mahour-iranian-date/" target="\_blank" rel="noopener noreferrer">Mahour</a> extension as an occasion, I’ll try to write a bit about Firefox extensions and how to build one.</p>

<h1 id="firefox-extensions">Firefox Extensions</h1>

<p>Before November 2017, various tools existed for building a Firefox extension, but in newer versions some of them — such as <code class="language-plaintext highlighter-rouge">overlay add-ons</code>, <code class="language-plaintext highlighter-rouge">bootstrapped add-ons</code>, <code class="language-plaintext highlighter-rouge">Add-on SDK</code>, and others — are no longer supported. New extensions must be built using <a href="https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions" target="\_blank" rel="noopener noreferrer">WebExtensions APIs</a>. If you have a <code class="language-plaintext highlighter-rouge">Legacy</code> extension and want to make it compatible with newer versions of Firefox, you can use <a href="https://extensionworkshop.com/documentation/develop/porting-a-legacy-firefox-extension/" target="\_blank" rel="noopener noreferrer">this Mozilla guide</a>.</p>

<h2 id="structure-of-webextensions">Structure of <code class="language-plaintext highlighter-rouge">WebExtension</code>s</h2>

<p>Every <code class="language-plaintext highlighter-rouge">WebExtension</code> has a <a href="https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/manifest.json" target="\_blank" rel="noopener noreferrer"><code class="language-plaintext highlighter-rouge">manifest.json</code></a> file that defines its structure, resources, and properties. The image below shows the general structure of the <code class="language-plaintext highlighter-rouge">manifest.json</code> file in a <code class="language-plaintext highlighter-rouge">WebExtension</code>.</p>

<div style="text-align: center;">
    <img src="/mahoor-update/webextension-anatomy.png" style="max-width: 80%; margin: 10px;" alt="WebExtension anatomy" />
</div>

<p>Sample <code class="language-plaintext highlighter-rouge">manifest.json</code> file for the Mahour extension:</p>

<div class="code-block">

<figure class="highlight"><pre><code class="language-json" data-lang="json"><span class="p">{</span><span class="w">
  </span><span class="nl">"manifest_version"</span><span class="p">:</span><span class="w"> </span><span class="mi">2</span><span class="p">,</span><span class="w">
  </span><span class="nl">"name"</span><span class="p">:</span><span class="w"> </span><span class="s2">"Mahour"</span><span class="p">,</span><span class="w">
  </span><span class="nl">"description"</span><span class="p">:</span><span class="w"> </span><span class="s2">"Adds a new Iranian(Persian/Jalali/Khorshidi) date column to ThunderBird."</span><span class="p">,</span><span class="w">
  </span><span class="nl">"version"</span><span class="p">:</span><span class="w"> </span><span class="s2">"1.1.2"</span><span class="p">,</span><span class="w">
  </span><span class="nl">"homepage_url"</span><span class="p">:</span><span class="w"> </span><span class="s2">"https://github.com/mhdzli/mahour"</span><span class="p">,</span><span class="w">
  </span><span class="nl">"author"</span><span class="p">:</span><span class="w"> </span><span class="s2">"M.Zeinali"</span><span class="p">,</span><span class="w">
  </span><span class="nl">"applications"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="nl">"gecko"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
      </span><span class="nl">"id"</span><span class="p">:</span><span class="w"> </span><span class="s2">"mahour@zmim.ir"</span><span class="p">,</span><span class="w">
      </span><span class="nl">"strict_min_version"</span><span class="p">:</span><span class="w"> </span><span class="s2">"68.0a1"</span><span class="w">
    </span><span class="p">}</span><span class="w">
  </span><span class="p">},</span><span class="w">
  </span><span class="nl">"[experiment_apis](experiment_apis)"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="nl">"MahourDate"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
      </span><span class="nl">"schema"</span><span class="p">:</span><span class="w"> </span><span class="s2">"api/schema.json"</span><span class="p">,</span><span class="w">
      </span><span class="nl">"parent"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
        </span><span class="nl">"scopes"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="s2">"addon_parent"</span><span class="p">],</span><span class="w">
        </span><span class="nl">"paths"</span><span class="p">:</span><span class="w"> </span><span class="p">[[</span><span class="s2">"MahourDate"</span><span class="p">]],</span><span class="w">
        </span><span class="nl">"script"</span><span class="p">:</span><span class="w"> </span><span class="s2">"api/experiments.js"</span><span class="w">
      </span><span class="p">}</span><span class="w">
    </span><span class="p">}</span><span class="w">
  </span><span class="p">},</span><span class="w">
  </span><span class="nl">"background"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="nl">"scripts"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="s2">"background/background.js"</span><span class="p">]</span><span class="w">
  </span><span class="p">},</span><span class="w">
  </span><span class="nl">"browser_action"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="nl">"default_title"</span><span class="p">:</span><span class="w"> </span><span class="s2">"ماهور"</span><span class="p">,</span><span class="w">
    </span><span class="nl">"default_popup"</span><span class="p">:</span><span class="w"> </span><span class="s2">"popup/popup.html"</span><span class="p">,</span><span class="w">
    </span><span class="nl">"default_icon"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
      </span><span class="nl">"48"</span><span class="p">:</span><span class="w"> </span><span class="s2">"assets/icons/icon48.png"</span><span class="w">
    </span><span class="p">}</span><span class="w">
  </span><span class="p">},</span><span class="w">
  </span><span class="nl">"options_ui"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="nl">"page"</span><span class="p">:</span><span class="w"> </span><span class="s2">"options/options.html"</span><span class="p">,</span><span class="w">
    </span><span class="nl">"open_in_tab"</span><span class="p">:</span><span class="w"> </span><span class="kc">false</span><span class="w">
  </span><span class="p">},</span><span class="w">
  </span><span class="nl">"icons"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="nl">"32"</span><span class="p">:</span><span class="w"> </span><span class="s2">"assets/icons/icon32.png"</span><span class="p">,</span><span class="w">
    </span><span class="nl">"48"</span><span class="p">:</span><span class="w"> </span><span class="s2">"assets/icons/icon48.png"</span><span class="p">,</span><span class="w">
    </span><span class="nl">"64"</span><span class="p">:</span><span class="w"> </span><span class="s2">"assets/icons/icon64.png"</span><span class="p">,</span><span class="w">
    </span><span class="nl">"128"</span><span class="p">:</span><span class="w"> </span><span class="s2">"assets/icons/icon128.png"</span><span class="w">
  </span><span class="p">},</span><span class="w">
  </span><span class="nl">"permissions"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="s2">"storage"</span><span class="p">]</span><span class="w">
</span><span class="p">}</span></code></pre></figure>

</div>

<p>This file contains the following keys:</p>

<ul>
  <li>Essential keys that must be defined:
    <ul>
      <li>Manifest version (<a href="https://developer.mozilla.org/en-US/Add-ons/WebExtensions/manifest.json/manifest_version" target="\_blank" rel="noopener noreferrer"><code class="language-plaintext highlighter-rouge">manifest_version</code></a>)</li>
      <li>Extension name (<a href="https://developer.mozilla.org/en-US/Add-ons/WebExtensions/manifest.json/name" target="\_blank" rel="noopener noreferrer"><code class="language-plaintext highlighter-rouge">name</code></a>)</li>
      <li>Extension version (<a href="https://developer.mozilla.org/en-US/Add-ons/WebExtensions/manifest.json/version" target="\_blank" rel="noopener noreferrer"><code class="language-plaintext highlighter-rouge">version</code></a>)</li>
    </ul>
  </li>
  <li>Optional keys:
    <ul>
      <li>Application description (<a href="https://developer.mozilla.org/en-US/Add-ons/WebExtensions/manifest.json/description" target="\_blank" rel="noopener noreferrer"><code class="language-plaintext highlighter-rouge">description</code></a>)</li>
      <li>Icons (<a href="https://developer.mozilla.org/en-US/Add-ons/WebExtensions/manifest.json/icons" target="\_blank" rel="noopener noreferrer"><code class="language-plaintext highlighter-rouge">icons</code></a>)</li>
      <li>Extension website or repository (<a href="https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/manifest.json/homepage_url" target="\_blank" rel="noopener noreferrer"><code class="language-plaintext highlighter-rouge">homepage_url</code></a>)</li>
      <li>Author (<a href="https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/manifest.json/author" target="\_blank" rel="noopener noreferrer"><code class="language-plaintext highlighter-rouge">author</code></a>)</li>
    </ul>
  </li>
  <li>Browser-specific settings (<a href="https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/manifest.json/browser_specific_settings" target="\_blank" rel="noopener noreferrer"><code class="language-plaintext highlighter-rouge">browser_specific_settings</code></a> or <code class="language-plaintext highlighter-rouge">applications</code> here). Browser properties required to run the extension <a href="https://developer.mozilla.org/en-US/Add-ons/WebExtensions/WebExtensions_and_the_Add-on_ID#When_do_you_need_an_add-on_ID" target="\_blank" rel="noopener noreferrer">when needed</a> are specified in this key:
    <ul>
      <li>Minimum version of the browser’s content rendering engine (<code class="language-plaintext highlighter-rouge">gecko</code>: content rendering engine)</li>
      <li>Extension ID for the content engine (<code class="language-plaintext highlighter-rouge">gecko.id</code>)</li>
    </ul>
  </li>
  <li>Background scripts (<a href="https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/Anatomy_of_a_WebExtension#Background_scripts" target="\_blank" rel="noopener noreferrer"><code class="language-plaintext highlighter-rouge">Background scripts</code></a>): these scripts run in the background in a special page called the <code class="language-plaintext highlighter-rouge">background page</code>.</li>
  <li>Content scripts (<a href="https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/manifest.json/content_scripts" target="\_blank" rel="noopener noreferrer"><code class="language-plaintext highlighter-rouge">Content scripts</code></a>) that do the main work and generate content for different parts of the extension. Mahour uses <a href="https://firefox-source-docs.mozilla.org/toolkit/components/extensions/webextensions/basics.html#webextensions-experiments" target="\_blank" rel="noopener noreferrer"><code class="language-plaintext highlighter-rouge">experiment_apis</code></a> instead of this key. The content script is loaded inside the API.</li>
  <li>Experimental API (<a href="https://firefox-source-docs.mozilla.org/toolkit/components/extensions/webextensions/basics.html?highlight=experiment_apis#webextensions-experiments" target="\_blank" rel="noopener noreferrer"><code class="language-plaintext highlighter-rouge">experiment_apis</code></a>): with this key, a new API can be created for use in the extension.</li>
  <li>Default pages that can use <code class="language-plaintext highlighter-rouge">css</code> and <code class="language-plaintext highlighter-rouge">js</code> files like a regular webpage:
    <ul>
      <li>Sidebars (<a href="https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/manifest.json/sidebar_action" target="\_blank" rel="noopener noreferrer"><code class="language-plaintext highlighter-rouge">Sidebars</code></a>)</li>
      <li>Popups — their properties are defined in the <a href="https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/manifest.json/page_action" target="\_blank" rel="noopener noreferrer"><code class="language-plaintext highlighter-rouge">browser_action || page_action</code></a> key.</li>
      <li>Options: their properties are defined in the <a href="https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/manifest.json/options_ui" target="\_blank" rel="noopener noreferrer"><code class="language-plaintext highlighter-rouge">options_ui</code></a> key. This key makes the <code class="language-plaintext highlighter-rouge">Preferences</code> option visible for the extension in the <code class="language-plaintext highlighter-rouge">Add-ons Manager</code> page. Clicking on it loads the options page in that section and allows changing extension properties. (Setting the <code class="language-plaintext highlighter-rouge">open_in_tab</code> property will open a new page for loading.)</li>
    </ul>
  </li>
  <li>Extension pages (<a href="https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/user_interface/Extension_pages" target="\_blank" rel="noopener noreferrer"><code class="language-plaintext highlighter-rouge">Extension pages</code></a>): each extension can also contain its own custom pages in addition to the default pages, which are defined here.</li>
  <li>Web accessible resources (<a href="https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/manifest.json/web_accessible_resources" target="\_blank" rel="noopener noreferrer"><code class="language-plaintext highlighter-rouge">Web accessible resources</code></a>): if we want to use <code class="language-plaintext highlighter-rouge">HTML</code>, <code class="language-plaintext highlighter-rouge">CSS</code>, <code class="language-plaintext highlighter-rouge">JavaScript</code>, etc. files in the extension’s content generation, we specify them here. (Example: if the extension needs to display images on web pages, we path them here so we can access them.)</li>
  <li>Permissions (<a href="https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/manifest.json/permissions" target="\_blank" rel="noopener noreferrer"><code class="language-plaintext highlighter-rouge">permissions</code></a>): the permissions required by the application are specified with this key. For optional permissions that won’t block execution if absent, the <a href="https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/manifest.json/optional_permissions" target="\_blank" rel="noopener noreferrer"><code class="language-plaintext highlighter-rouge">optional_permissions</code></a> key is used.</li>
</ul>

<h3 id="extension-apis">Extension APIs</h3>

<p>To better understand Mozilla’s <code class="language-plaintext highlighter-rouge">APIs</code> and how to use them, read <a href="https://developer.mozilla.org/en-US/Add-ons/WebExtensions" target="\_blank" rel="noopener noreferrer">this guide</a>.</p>

<p>Below is an example of an <code class="language-plaintext highlighter-rouge">API</code> defined in <code class="language-plaintext highlighter-rouge">manifest.json</code>:</p>

<div class="code-block">

<figure class="highlight"><pre><code class="language-json" data-lang="json"><span class="p">{</span><span class="w">
  </span><span class="nl">"manifest_version"</span><span class="p">:</span><span class="w"> </span><span class="mi">2</span><span class="p">,</span><span class="w">
  </span><span class="nl">"name"</span><span class="p">:</span><span class="w"> </span><span class="s2">"Extension containing an experimental API"</span><span class="p">,</span><span class="w">
  </span><span class="nl">"experiment_apis"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="nl">"apiname"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
      </span><span class="nl">"schema"</span><span class="p">:</span><span class="w"> </span><span class="s2">"schema.json"</span><span class="p">,</span><span class="w">
      </span><span class="nl">"parent"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
        </span><span class="nl">"scopes"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="s2">"addon_parent"</span><span class="p">],</span><span class="w">
        </span><span class="nl">"paths"</span><span class="p">:</span><span class="w"> </span><span class="p">[[</span><span class="s2">"myapi"</span><span class="p">]],</span><span class="w">
        </span><span class="nl">"script"</span><span class="p">:</span><span class="w"> </span><span class="s2">"implementation.js"</span><span class="w">
      </span><span class="p">},</span><span class="w">

      </span><span class="nl">"child"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
        </span><span class="nl">"scopes"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="s2">"addon_child"</span><span class="p">],</span><span class="w">
        </span><span class="nl">"paths"</span><span class="p">:</span><span class="w"> </span><span class="p">[[</span><span class="s2">"myapi"</span><span class="p">]],</span><span class="w">
        </span><span class="nl">"script"</span><span class="p">:</span><span class="w"> </span><span class="s2">"child-implementation.js"</span><span class="w">
      </span><span class="p">}</span><span class="w">
    </span><span class="p">}</span><span class="w">

</span><span class="p">}</span><span class="w">
</span><span class="p">}</span></code></pre></figure>


</div>

<p>In each <code class="language-plaintext highlighter-rouge">API</code>, one or more namespaces are defined, whose objects can be called in extension scripts. A <code class="language-plaintext highlighter-rouge">schema</code> must be defined for each <code class="language-plaintext highlighter-rouge">API</code> that specifies the <code class="language-plaintext highlighter-rouge">API</code>’s properties. The <code class="language-plaintext highlighter-rouge">schema</code> key in <code class="language-plaintext highlighter-rouge">manifest.json</code> specifies a relative path within the extension’s root for the <code class="language-plaintext highlighter-rouge">schema</code> file.</p>

<p>The main properties of the parent process and child processes are specified with the <code class="language-plaintext highlighter-rouge">parent</code> and <code class="language-plaintext highlighter-rouge">child</code> keys and their <code class="language-plaintext highlighter-rouge">script</code> paths.</p>

<p>Currently, the only allowed options for the <code class="language-plaintext highlighter-rouge">scopes</code> key for specifying the access scope of each namespace are <code class="language-plaintext highlighter-rouge">addon-child</code> and <code class="language-plaintext highlighter-rouge">addon-parent</code>.</p>

<p>The <code class="language-plaintext highlighter-rouge">schema</code> file for the Mahour extension:</p>

<div class="code-block">

<figure class="highlight"><pre><code class="language-json" data-lang="json"><span class="p">[</span><span class="w">
  </span><span class="p">{</span><span class="w">
    </span><span class="nl">"namespace"</span><span class="p">:</span><span class="w"> </span><span class="s2">"MahourDate"</span><span class="p">,</span><span class="w">
    </span><span class="nl">"functions"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w">
      </span><span class="p">{</span><span class="w">
        </span><span class="nl">"name"</span><span class="p">:</span><span class="w"> </span><span class="s2">"addWindowListener"</span><span class="p">,</span><span class="w">
        </span><span class="nl">"type"</span><span class="p">:</span><span class="w"> </span><span class="s2">"function"</span><span class="p">,</span><span class="w">
        </span><span class="nl">"description"</span><span class="p">:</span><span class="w"> </span><span class="s2">"Adds a listener 3pane windows"</span><span class="p">,</span><span class="w">
        </span><span class="nl">"async"</span><span class="p">:</span><span class="w"> </span><span class="kc">false</span><span class="p">,</span><span class="w">
        </span><span class="nl">"parameters"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w">
          </span><span class="p">{</span><span class="w">
            </span><span class="nl">"name"</span><span class="p">:</span><span class="w"> </span><span class="s2">"hich"</span><span class="p">,</span><span class="w">
            </span><span class="nl">"type"</span><span class="p">:</span><span class="w"> </span><span class="s2">"string"</span><span class="p">,</span><span class="w">
            </span><span class="nl">"description"</span><span class="p">:</span><span class="w"> </span><span class="s2">"hich"</span><span class="w">
          </span><span class="p">}</span><span class="w">
        </span><span class="p">]</span><span class="w">
      </span><span class="p">},</span><span class="w">
      </span><span class="p">{</span><span class="w">
        </span><span class="nl">"name"</span><span class="p">:</span><span class="w"> </span><span class="s2">"changeSettings"</span><span class="p">,</span><span class="w">
        </span><span class="nl">"type"</span><span class="p">:</span><span class="w"> </span><span class="s2">"function"</span><span class="p">,</span><span class="w">
        </span><span class="nl">"description"</span><span class="p">:</span><span class="w"> </span><span class="s2">"Handle Prefrences"</span><span class="p">,</span><span class="w">
        </span><span class="nl">"async"</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="p">,</span><span class="w">
        </span><span class="nl">"parameters"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w">
          </span><span class="p">{</span><span class="w">
            </span><span class="nl">"name"</span><span class="p">:</span><span class="w"> </span><span class="s2">"newSettings"</span><span class="p">,</span><span class="w">
            </span><span class="nl">"type"</span><span class="p">:</span><span class="w"> </span><span class="s2">"object"</span><span class="p">,</span><span class="w">
            </span><span class="nl">"properties"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
              </span><span class="nl">"longMonth"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="nl">"type"</span><span class="p">:</span><span class="w"> </span><span class="s2">"boolean"</span><span class="w"> </span><span class="p">},</span><span class="w">
              </span><span class="nl">"showTime"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="nl">"type"</span><span class="p">:</span><span class="w"> </span><span class="s2">"boolean"</span><span class="w"> </span><span class="p">},</span><span class="w">
              </span><span class="nl">"weekDay"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="nl">"type"</span><span class="p">:</span><span class="w"> </span><span class="s2">"boolean"</span><span class="w"> </span><span class="p">},</span><span class="w">
              </span><span class="nl">"englishNumbers"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="nl">"type"</span><span class="p">:</span><span class="w"> </span><span class="s2">"boolean"</span><span class="w"> </span><span class="p">}</span><span class="w">
            </span><span class="p">}</span><span class="w">
          </span><span class="p">}</span><span class="w">
        </span><span class="p">]</span><span class="w">
      </span><span class="p">}</span><span class="w">
    </span><span class="p">]</span><span class="w">
  </span><span class="p">}</span><span class="w">
</span><span class="p">]</span></code></pre></figure>

</div>

<p>In this file, two functions <code class="language-plaintext highlighter-rouge">addWindowListener</code> and <code class="language-plaintext highlighter-rouge">changeSettings</code> are defined for the <code class="language-plaintext highlighter-rouge">MahourDate</code> namespace. The first function adds a <code class="language-plaintext highlighter-rouge">windowListener</code> to control the display of the <code class="language-plaintext highlighter-rouge">Date</code> column at program startup. The second function rebuilds and displays the <code class="language-plaintext highlighter-rouge">Date</code> column when the settings are changed.</p>

<blockquote>
  <p>Note: Calling these functions in the <code class="language-plaintext highlighter-rouge">background script</code> must be accompanied by a variable. Therefore, even though <code class="language-plaintext highlighter-rouge">addWindowListener</code> doesn’t need any input, a variable named <code class="language-plaintext highlighter-rouge">hich</code> (meaning “nothing” 😂) is defined for it.</p>
</blockquote>

<p>These functions are defined inside the <code class="language-plaintext highlighter-rouge">api/experiments.js</code> script, which is referenced in the <code class="language-plaintext highlighter-rouge">parent</code> property of the <code class="language-plaintext highlighter-rouge">MahourDate</code> API in the extension’s <code class="language-plaintext highlighter-rouge">manifest.json</code> file.</p>

<p>The <code class="language-plaintext highlighter-rouge">experiment.js</code> file:</p>

<div class="code-block">

<figure class="highlight"><pre><code class="language-javascript" data-lang="javascript"><span class="c1">// This Source Code Form is subject to the terms of the</span>
<span class="c1">// GNU General Public License, version 3.0.</span>

<span class="dl">"</span><span class="s2">use strict</span><span class="dl">"</span><span class="p">;</span>

<span class="kd">var</span> <span class="p">{</span> <span class="nx">Services</span> <span class="p">}</span> <span class="o">=</span> <span class="nx">ChromeUtils</span><span class="p">.</span><span class="k">import</span><span class="p">(</span><span class="dl">"</span><span class="s2">resource://gre/modules/Services.jsm</span><span class="dl">"</span><span class="p">);</span>
<span class="kd">var</span> <span class="p">{</span> <span class="nx">ExtensionSupport</span> <span class="p">}</span> <span class="o">=</span> <span class="nx">ChromeUtils</span><span class="p">.</span><span class="k">import</span><span class="p">(</span>
<span class="dl">"</span><span class="s2">resource:///modules/ExtensionSupport.jsm</span><span class="dl">"</span>
<span class="p">);</span>
<span class="kd">var</span> <span class="p">{</span> <span class="nx">ExtensionParent</span> <span class="p">}</span> <span class="o">=</span> <span class="nx">ChromeUtils</span><span class="p">.</span><span class="k">import</span><span class="p">(</span>
<span class="dl">"</span><span class="s2">resource://gre/modules/ExtensionParent.jsm</span><span class="dl">"</span>
<span class="p">);</span>

<span class="kd">const</span> <span class="nx">EXTENSION_NAME</span> <span class="o">=</span> <span class="dl">"</span><span class="s2">mahour@zmim.ir</span><span class="dl">"</span><span class="p">;</span>
<span class="kd">var</span> <span class="nx">extension</span> <span class="o">=</span> <span class="nx">ExtensionParent</span><span class="p">.</span><span class="nx">GlobalManager</span><span class="p">.</span><span class="nf">getExtension</span><span class="p">(</span><span class="nx">EXTENSION_NAME</span><span class="p">);</span>

<span class="c1">//customizeable options</span>
<span class="kd">var</span> <span class="nx">monthStyle</span> <span class="o">=</span> <span class="dl">"</span><span class="s2">long</span><span class="dl">"</span><span class="p">;</span>
<span class="kd">var</span> <span class="nx">timeStyle</span> <span class="o">=</span> <span class="dl">"</span><span class="s2">2-digit</span><span class="dl">"</span><span class="p">;</span>
<span class="kd">var</span> <span class="nx">weekDayStyle</span> <span class="o">=</span> <span class="dl">"</span><span class="s2">hidden</span><span class="dl">"</span><span class="p">;</span>
<span class="kd">var</span> <span class="nx">numbersStyle</span> <span class="o">=</span> <span class="dl">"</span><span class="s2">arabext</span><span class="dl">"</span><span class="p">;</span>

<span class="c1">// Implements the functions defined in the experiments section of schema.json.</span>
<span class="kd">var</span> <span class="nx">MahourDate</span> <span class="o">=</span> <span class="kd">class</span> <span class="nc">extends</span> <span class="nx">ExtensionCommon</span><span class="p">.</span><span class="nx">ExtensionAPI</span> <span class="p">{</span>
<span class="nf">onStartup</span><span class="p">()</span> <span class="p">{}</span>

<span class="nf">onShutdown</span><span class="p">(</span><span class="nx">isAppShutdown</span><span class="p">)</span> <span class="p">{</span>
<span class="k">if </span><span class="p">(</span><span class="nx">isAppShutdown</span><span class="p">)</span> <span class="k">return</span><span class="p">;</span>
<span class="nx">Services</span><span class="p">.</span><span class="nx">obs</span><span class="p">.</span><span class="nf">notifyObservers</span><span class="p">(</span><span class="kc">null</span><span class="p">,</span> <span class="dl">"</span><span class="s2">startupcache-invalidate</span><span class="dl">"</span><span class="p">);</span>
<span class="p">}</span>

<span class="nf">getAPI</span><span class="p">(</span><span class="nx">context</span><span class="p">)</span> <span class="p">{</span>
<span class="nx">context</span><span class="p">.</span><span class="nf">callOnClose</span><span class="p">(</span><span class="k">this</span><span class="p">);</span>
<span class="k">return</span> <span class="p">{</span>
<span class="na">MahourDate</span><span class="p">:</span> <span class="p">{</span>
<span class="nf">addWindowListener</span><span class="p">(</span><span class="nx">hich</span><span class="p">)</span> <span class="p">{</span>
<span class="nx">ExtensionSupport</span><span class="p">.</span><span class="nf">registerWindowListener</span><span class="p">(</span><span class="nx">EXTENSION_NAME</span><span class="p">,</span> <span class="p">{</span>
<span class="na">chromeURLs</span><span class="p">:</span> <span class="p">[</span>
<span class="dl">"</span><span class="s2">chrome://messenger/content/messenger.xul</span><span class="dl">"</span><span class="p">,</span>
<span class="dl">"</span><span class="s2">chrome://messenger/content/messenger.xhtml</span><span class="dl">"</span><span class="p">,</span>
<span class="p">],</span>
<span class="na">onLoadWindow</span><span class="p">:</span> <span class="nx">paint</span><span class="p">,</span>
<span class="na">onUnloadWindow</span><span class="p">:</span> <span class="nx">unpaint</span><span class="p">,</span>
<span class="p">});</span>
<span class="p">},</span>
<span class="nf">changeSettings</span><span class="p">(</span><span class="nx">newSettings</span><span class="p">)</span> <span class="p">{</span>
<span class="k">if </span><span class="p">(</span><span class="nx">newSettings</span><span class="p">.</span><span class="nx">longMonth</span><span class="p">)</span> <span class="p">{</span>
<span class="nx">monthStyle</span> <span class="o">=</span> <span class="dl">"</span><span class="s2">long</span><span class="dl">"</span><span class="p">;</span>
<span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
<span class="nx">monthStyle</span> <span class="o">=</span> <span class="dl">"</span><span class="s2">2-digit</span><span class="dl">"</span><span class="p">;</span>
<span class="p">}</span>
<span class="k">if </span><span class="p">(</span><span class="nx">newSettings</span><span class="p">.</span><span class="nx">showTime</span><span class="p">)</span> <span class="p">{</span>
<span class="nx">timeStyle</span> <span class="o">=</span> <span class="dl">"</span><span class="s2">2-digit</span><span class="dl">"</span><span class="p">;</span>
<span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
<span class="nx">timeStyle</span> <span class="o">=</span> <span class="dl">"</span><span class="s2">hidden</span><span class="dl">"</span><span class="p">;</span>
<span class="p">}</span>
<span class="k">if </span><span class="p">(</span><span class="nx">newSettings</span><span class="p">.</span><span class="nx">weekDay</span><span class="p">)</span> <span class="p">{</span>
<span class="nx">weekDayStyle</span> <span class="o">=</span> <span class="dl">"</span><span class="s2">long</span><span class="dl">"</span><span class="p">;</span>
<span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
<span class="nx">weekDayStyle</span> <span class="o">=</span> <span class="dl">"</span><span class="s2">hidden</span><span class="dl">"</span><span class="p">;</span>
<span class="p">}</span>
<span class="k">if </span><span class="p">(</span><span class="nx">newSettings</span><span class="p">.</span><span class="nx">englishNumbers</span><span class="p">)</span> <span class="p">{</span>
<span class="nx">numbersStyle</span> <span class="o">=</span> <span class="dl">"</span><span class="s2">latn</span><span class="dl">"</span><span class="p">;</span>
<span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
<span class="nx">numbersStyle</span> <span class="o">=</span> <span class="dl">"</span><span class="s2">arabext</span><span class="dl">"</span><span class="p">;</span>
<span class="p">}</span>
<span class="k">for </span><span class="p">(</span><span class="kd">let</span> <span class="nx">win</span> <span class="k">of</span> <span class="nx">Services</span><span class="p">.</span><span class="nx">wm</span><span class="p">.</span><span class="nf">getEnumerator</span><span class="p">(</span><span class="dl">"</span><span class="s2">mail:3pane</span><span class="dl">"</span><span class="p">))</span> <span class="p">{</span>
<span class="nx">win</span><span class="p">.</span><span class="nx">MahourDate</span><span class="p">.</span><span class="nx">MahourDateHeaderView</span><span class="p">.</span><span class="nf">destroy</span><span class="p">();</span>
<span class="nx">win</span><span class="p">.</span><span class="nx">MahourDate</span><span class="p">.</span><span class="nx">MahourDateHeaderView</span><span class="p">.</span><span class="nf">init</span><span class="p">(</span><span class="nx">win</span><span class="p">);</span>
<span class="p">}</span>
<span class="p">},</span>
<span class="p">},</span>
<span class="p">};</span>
<span class="p">}</span>

<span class="nf">close</span><span class="p">()</span> <span class="p">{</span>
<span class="nx">ExtensionSupport</span><span class="p">.</span><span class="nf">unregisterWindowListener</span><span class="p">(</span><span class="nx">EXTENSION_NAME</span><span class="p">);</span>
<span class="k">for </span><span class="p">(</span><span class="kd">let</span> <span class="nx">win</span> <span class="k">of</span> <span class="nx">Services</span><span class="p">.</span><span class="nx">wm</span><span class="p">.</span><span class="nf">getEnumerator</span><span class="p">(</span><span class="dl">"</span><span class="s2">mail:3pane</span><span class="dl">"</span><span class="p">))</span> <span class="p">{</span>
<span class="nf">unpaint</span><span class="p">(</span><span class="nx">win</span><span class="p">);</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="p">};</span>

<span class="kd">function</span> <span class="nf">paint</span><span class="p">(</span><span class="nx">win</span><span class="p">)</span> <span class="p">{</span>
<span class="nx">win</span><span class="p">.</span><span class="nx">MahourDate</span> <span class="o">=</span> <span class="p">{};</span>
<span class="nx">Services</span><span class="p">.</span><span class="nx">scriptloader</span><span class="p">.</span><span class="nf">loadSubScript</span><span class="p">(</span>
<span class="nx">extension</span><span class="p">.</span><span class="nf">getURL</span><span class="p">(</span><span class="dl">"</span><span class="s2">content/customcol.js</span><span class="dl">"</span><span class="p">),</span>
<span class="nx">win</span><span class="p">.</span><span class="nx">MahourDate</span>
<span class="p">);</span>
<span class="nx">win</span><span class="p">.</span><span class="nx">MahourDate</span><span class="p">.</span><span class="nx">MahourDateHeaderView</span><span class="p">.</span><span class="nf">init</span><span class="p">(</span><span class="nx">win</span><span class="p">);</span>
<span class="p">}</span>

<span class="kd">function</span> <span class="nf">unpaint</span><span class="p">(</span><span class="nx">win</span><span class="p">)</span> <span class="p">{</span>
<span class="nx">win</span><span class="p">.</span><span class="nx">MahourDate</span><span class="p">.</span><span class="nx">MahourDateHeaderView</span><span class="p">.</span><span class="nf">destroy</span><span class="p">();</span>
<span class="k">delete</span> <span class="nx">win</span><span class="p">.</span><span class="nx">MahourDate</span><span class="p">;</span>
<span class="p">}</span></code></pre></figure>


</div>

<p>To be able to call the <code class="language-plaintext highlighter-rouge">addWindowListener</code> and <code class="language-plaintext highlighter-rouge">changeSettings</code> functions, they are defined using the pattern specified in the <a href="https://firefox-source-docs.mozilla.org/toolkit/components/extensions/webextensions/functions.html" target="\_blank" rel="noopener noreferrer">Mozilla guide</a> inside <code class="language-plaintext highlighter-rouge">getAPI(context)</code>.</p>

<p>The <code class="language-plaintext highlighter-rouge">addWindowListener</code> function calls <code class="language-plaintext highlighter-rouge">paint</code> and <code class="language-plaintext highlighter-rouge">unpaint</code> functions via the <code class="language-plaintext highlighter-rouge">onLoadWindow</code> and <code class="language-plaintext highlighter-rouge">onUnloadWindow</code> events respectively. The <code class="language-plaintext highlighter-rouge">paint</code> function calls the <code class="language-plaintext highlighter-rouge">content/customcol.js</code> script to generate the column content and display it. The <code class="language-plaintext highlighter-rouge">unpaint</code> function stops the column display and clears its content.</p>

<p>The default values for the configurable variables are defined here. When they are changed in the extension’s options page, the <code class="language-plaintext highlighter-rouge">changeSetting</code> function is called via <code class="language-plaintext highlighter-rouge">background.js</code>, which rebuilds and displays the date column.</p>

<p>The <code class="language-plaintext highlighter-rouge">content/customcol.js</code> file:</p>

<div class="code-block">

<figure class="highlight"><pre><code class="language-javascript" data-lang="javascript"><span class="c1">// This Source Code Form is subject to the terms of the</span>
<span class="c1">// GNU General Public License, version 3.0.</span>
<span class="kd">var</span> <span class="p">{</span> <span class="nx">AppConstants</span> <span class="p">}</span> <span class="o">=</span> <span class="nx">ChromeUtils</span><span class="p">.</span><span class="k">import</span><span class="p">(</span>
  <span class="dl">"</span><span class="s2">resource://gre/modules/AppConstants.jsm</span><span class="dl">"</span>
<span class="p">);</span>
<span class="kd">var</span> <span class="p">{</span> <span class="nx">Services</span> <span class="p">}</span> <span class="o">=</span> <span class="nx">ChromeUtils</span><span class="p">.</span><span class="k">import</span><span class="p">(</span><span class="dl">"</span><span class="s2">resource://gre/modules/Services.jsm</span><span class="dl">"</span><span class="p">);</span>

<span class="kd">const</span> <span class="nx">jalaliDateColumnHandler</span> <span class="o">=</span> <span class="p">{</span>
<span class="nf">init</span><span class="p">(</span><span class="nx">win</span><span class="p">)</span> <span class="p">{</span>
<span class="k">this</span><span class="p">.</span><span class="nx">win</span> <span class="o">=</span> <span class="nx">win</span><span class="p">;</span>
<span class="p">},</span>
<span class="nf">getCellText</span><span class="p">(</span><span class="nx">row</span><span class="p">,</span> <span class="nx">col</span><span class="p">)</span> <span class="p">{</span>
<span class="kd">var</span> <span class="nx">date</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">Date</span><span class="p">(</span><span class="k">this</span><span class="p">.</span><span class="nf">getJalaliDate</span><span class="p">(</span><span class="k">this</span><span class="p">.</span><span class="nx">win</span><span class="p">.</span><span class="nx">gDBView</span><span class="p">.</span><span class="nf">getMsgHdrAt</span><span class="p">(</span><span class="nx">row</span><span class="p">)));</span>
<span class="kd">var</span> <span class="nx">currentDate</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">Date</span><span class="p">();</span>

    <span class="c1">//fixed options</span>
    <span class="kd">var</span> <span class="nx">yearStyle</span> <span class="o">=</span> <span class="dl">"</span><span class="s2">2-digit</span><span class="dl">"</span><span class="p">;</span>
    <span class="kd">var</span> <span class="nx">dayStyle</span> <span class="o">=</span> <span class="dl">"</span><span class="s2">2-digit</span><span class="dl">"</span><span class="p">;</span>

    <span class="kd">var</span> <span class="nx">locale</span> <span class="o">=</span> <span class="dl">"</span><span class="s2">fa-IR-u-nu-</span><span class="dl">"</span> <span class="o">+</span> <span class="nx">numbersStyle</span> <span class="o">+</span> <span class="dl">"</span><span class="s2">-ca-persian</span><span class="dl">"</span><span class="p">;</span>

    <span class="kd">var</span> <span class="nx">year</span> <span class="o">=</span> <span class="nx">date</span><span class="p">.</span><span class="nf">toLocaleString</span><span class="p">(</span><span class="nx">locale</span><span class="p">,</span> <span class="p">{</span> <span class="na">year</span><span class="p">:</span> <span class="nx">yearStyle</span> <span class="p">});</span>
    <span class="kd">var</span> <span class="nx">month</span> <span class="o">=</span> <span class="nx">date</span><span class="p">.</span><span class="nf">toLocaleString</span><span class="p">(</span><span class="nx">locale</span><span class="p">,</span> <span class="p">{</span> <span class="na">month</span><span class="p">:</span> <span class="nx">monthStyle</span> <span class="p">});</span>
    <span class="kd">var</span> <span class="nx">day</span> <span class="o">=</span> <span class="nx">date</span><span class="p">.</span><span class="nf">toLocaleString</span><span class="p">(</span><span class="nx">locale</span><span class="p">,</span> <span class="p">{</span> <span class="na">day</span><span class="p">:</span> <span class="nx">dayStyle</span> <span class="p">});</span>
    <span class="kd">var</span> <span class="nx">weekDay</span> <span class="o">=</span>
      <span class="nx">weekDayStyle</span> <span class="o">!=</span> <span class="dl">"</span><span class="s2">hidden</span><span class="dl">"</span>
        <span class="p">?</span> <span class="nx">date</span><span class="p">.</span><span class="nf">toLocaleString</span><span class="p">(</span><span class="nx">locale</span><span class="p">,</span> <span class="p">{</span> <span class="na">weekday</span><span class="p">:</span> <span class="nx">weekDayStyle</span> <span class="p">})</span>
        <span class="p">:</span> <span class="dl">""</span><span class="p">;</span>
    <span class="kd">var</span> <span class="nx">time</span> <span class="o">=</span>
      <span class="nx">timeStyle</span> <span class="o">!=</span> <span class="dl">"</span><span class="s2">hidden</span><span class="dl">"</span>
        <span class="p">?</span> <span class="nx">date</span><span class="p">.</span><span class="nf">toLocaleString</span><span class="p">(</span><span class="nx">locale</span><span class="p">,</span> <span class="p">{</span>
            <span class="na">hour</span><span class="p">:</span> <span class="nx">timeStyle</span><span class="p">,</span>
            <span class="na">minute</span><span class="p">:</span> <span class="nx">timeStyle</span><span class="p">,</span>
            <span class="na">hour12</span><span class="p">:</span> <span class="kc">false</span><span class="p">,</span>
          <span class="p">})</span> <span class="o">+</span> <span class="dl">"</span><span class="s2"> ،</span><span class="dl">"</span>
        <span class="p">:</span> <span class="dl">""</span><span class="p">;</span>

    <span class="c1">//fix for bug that doesn't prepend zero to farsei</span>
    <span class="k">if </span><span class="p">(</span><span class="nx">time</span><span class="p">.</span><span class="nx">length</span> <span class="o">!=</span> <span class="mi">7</span> <span class="o">&amp;&amp;</span> <span class="nx">timeStyle</span> <span class="o">!=</span> <span class="dl">"</span><span class="s2">hidden</span><span class="dl">"</span><span class="p">)</span> <span class="p">{</span>
      <span class="kd">var</span> <span class="nx">zero</span> <span class="o">=</span> <span class="nx">numbersStyle</span> <span class="o">===</span> <span class="dl">"</span><span class="s2">arabext</span><span class="dl">"</span> <span class="p">?</span> <span class="dl">"</span><span class="s2">۰</span><span class="dl">"</span> <span class="p">:</span> <span class="dl">"</span><span class="s2">0</span><span class="dl">"</span><span class="p">;</span>
      <span class="nx">time</span> <span class="o">=</span> <span class="nx">zero</span> <span class="o">+</span> <span class="nx">time</span><span class="p">;</span>
    <span class="p">}</span>
    <span class="kd">var</span> <span class="nx">isCurrentYear</span><span class="p">;</span>
    <span class="k">if </span><span class="p">(</span><span class="nx">currentDate</span><span class="p">.</span><span class="nf">toLocaleString</span><span class="p">(</span><span class="nx">locale</span><span class="p">,</span> <span class="p">{</span> <span class="na">year</span><span class="p">:</span> <span class="nx">yearStyle</span> <span class="p">})</span> <span class="o">==</span> <span class="nx">year</span><span class="p">)</span> <span class="p">{</span>
      <span class="nx">isCurrentYear</span> <span class="o">=</span> <span class="kc">true</span><span class="p">;</span>
    <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
      <span class="nx">isCurrentYear</span> <span class="o">=</span> <span class="kc">false</span><span class="p">;</span>
    <span class="p">}</span>
    <span class="kd">var</span> <span class="nx">isCurrentDay</span><span class="p">;</span>
    <span class="k">if </span><span class="p">(</span><span class="nx">date</span><span class="p">.</span><span class="nf">toDateString</span><span class="p">()</span> <span class="o">===</span> <span class="nx">currentDate</span><span class="p">.</span><span class="nf">toDateString</span><span class="p">())</span> <span class="p">{</span>
      <span class="nx">isCurrentDay</span> <span class="o">=</span> <span class="kc">true</span><span class="p">;</span>
    <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
      <span class="nx">isCurrentDay</span> <span class="o">=</span> <span class="kc">false</span><span class="p">;</span>
    <span class="p">}</span>
    <span class="kd">var</span> <span class="nx">isYesterday</span><span class="p">;</span>
    <span class="kd">var</span> <span class="nx">yesterdayDate</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">Date</span><span class="p">();</span>
    <span class="nx">yesterdayDate</span><span class="p">.</span><span class="nf">setDate</span><span class="p">(</span><span class="nx">currentDate</span><span class="p">.</span><span class="nf">getDate</span><span class="p">()</span> <span class="o">-</span> <span class="mi">1</span><span class="p">);</span>
    <span class="k">if </span><span class="p">(</span><span class="nx">date</span><span class="p">.</span><span class="nf">toDateString</span><span class="p">()</span> <span class="o">===</span> <span class="nx">yesterdayDate</span><span class="p">.</span><span class="nf">toDateString</span><span class="p">())</span> <span class="p">{</span>
      <span class="nx">isYesterday</span> <span class="o">=</span> <span class="kc">true</span><span class="p">;</span>
    <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
      <span class="nx">isYesterday</span> <span class="o">=</span> <span class="kc">false</span><span class="p">;</span>
    <span class="p">}</span>

    <span class="kd">var</span> <span class="nx">placehodler</span><span class="p">;</span>
    <span class="k">if </span><span class="p">(</span><span class="nx">monthStyle</span> <span class="o">===</span> <span class="dl">"</span><span class="s2">long</span><span class="dl">"</span><span class="p">)</span> <span class="p">{</span>
      <span class="nx">placeholder</span> <span class="o">=</span> <span class="dl">"</span><span class="s2">TT </span><span class="se">\</span><span class="s2">u202BWD DD MM YY</span><span class="se">\</span><span class="s2">u202C</span><span class="dl">"</span><span class="p">;</span>
    <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
      <span class="nx">placeholder</span> <span class="o">=</span> <span class="dl">"</span><span class="s2">TT YY/MM/DD WD</span><span class="dl">"</span><span class="p">;</span>
    <span class="p">}</span>

    <span class="c1">//remove year if it's current year</span>
    <span class="k">if </span><span class="p">(</span><span class="nx">isCurrentYear</span><span class="p">)</span> <span class="p">{</span>
      <span class="nx">placeholder</span> <span class="o">=</span> <span class="nx">placeholder</span><span class="p">.</span><span class="nf">replace</span><span class="p">(</span><span class="sr">/YY./</span><span class="p">,</span> <span class="dl">""</span><span class="p">);</span>
    <span class="p">}</span>
    <span class="c1">//only show time if it's current day or yesterday</span>
    <span class="k">if </span><span class="p">(</span><span class="nx">isCurrentDay</span><span class="p">)</span> <span class="p">{</span>
      <span class="nx">placeholder</span> <span class="o">=</span> <span class="dl">"</span><span class="s2">TT Today</span><span class="dl">"</span><span class="p">;</span>
    <span class="p">}</span> <span class="k">else</span> <span class="k">if </span><span class="p">(</span><span class="nx">isYesterday</span><span class="p">)</span> <span class="p">{</span>
      <span class="nx">placeholder</span> <span class="o">=</span> <span class="dl">"</span><span class="s2">TT Yesterday</span><span class="dl">"</span><span class="p">;</span>
    <span class="p">}</span>

    <span class="nx">dateString</span> <span class="o">=</span> <span class="nx">placeholder</span>
      <span class="p">.</span><span class="nf">replace</span><span class="p">(</span><span class="dl">"</span><span class="s2">YY</span><span class="dl">"</span><span class="p">,</span> <span class="nx">year</span><span class="p">)</span>
      <span class="p">.</span><span class="nf">replace</span><span class="p">(</span><span class="dl">"</span><span class="s2">MM</span><span class="dl">"</span><span class="p">,</span> <span class="nx">month</span><span class="p">)</span>
      <span class="p">.</span><span class="nf">replace</span><span class="p">(</span><span class="dl">"</span><span class="s2">DD</span><span class="dl">"</span><span class="p">,</span> <span class="nx">day</span><span class="p">)</span>
      <span class="p">.</span><span class="nf">replace</span><span class="p">(</span><span class="dl">"</span><span class="s2">WD</span><span class="dl">"</span><span class="p">,</span> <span class="nx">weekDay</span><span class="p">)</span>
      <span class="p">.</span><span class="nf">replace</span><span class="p">(</span><span class="dl">"</span><span class="s2">TT</span><span class="dl">"</span><span class="p">,</span> <span class="nx">time</span><span class="p">);</span>

    <span class="k">return</span> <span class="nx">dateString</span><span class="p">;</span>

<span class="p">},</span>
<span class="nf">getSortStringForRow</span><span class="p">(</span><span class="nx">hdr</span><span class="p">)</span> <span class="p">{</span>
<span class="k">return</span> <span class="k">this</span><span class="p">.</span><span class="nf">getJalaliDate</span><span class="p">(</span><span class="nx">hdr</span><span class="p">);</span>
<span class="p">},</span>
<span class="nf">isString</span><span class="p">()</span> <span class="p">{</span>
<span class="k">return</span> <span class="kc">true</span><span class="p">;</span>
<span class="p">},</span>
<span class="nf">getCellProperties</span><span class="p">(</span><span class="nx">row</span><span class="p">,</span> <span class="nx">col</span><span class="p">,</span> <span class="nx">props</span><span class="p">)</span> <span class="p">{},</span>
<span class="nf">getRowProperties</span><span class="p">(</span><span class="nx">row</span><span class="p">,</span> <span class="nx">props</span><span class="p">)</span> <span class="p">{},</span>
<span class="nf">getImageSrc</span><span class="p">(</span><span class="nx">row</span><span class="p">,</span> <span class="nx">col</span><span class="p">)</span> <span class="p">{</span>
<span class="k">return</span> <span class="kc">null</span><span class="p">;</span>
<span class="p">},</span>
<span class="nf">getSortLongForRow</span><span class="p">(</span><span class="nx">hdr</span><span class="p">)</span> <span class="p">{</span>
<span class="k">return</span> <span class="mi">0</span><span class="p">;</span>
<span class="p">},</span>
<span class="nf">getJalaliDate</span><span class="p">(</span><span class="nx">aHeader</span><span class="p">)</span> <span class="p">{</span>
<span class="k">return</span> <span class="nx">aHeader</span><span class="p">.</span><span class="nx">date</span> <span class="o">/</span> <span class="mi">1000</span><span class="p">;</span>
<span class="p">},</span>
<span class="p">};</span>

<span class="kd">const</span> <span class="nx">columnOverlay</span> <span class="o">=</span> <span class="p">{</span>
<span class="nf">init</span><span class="p">(</span><span class="nx">win</span><span class="p">)</span> <span class="p">{</span>
<span class="k">this</span><span class="p">.</span><span class="nx">win</span> <span class="o">=</span> <span class="nx">win</span><span class="p">;</span>
<span class="k">this</span><span class="p">.</span><span class="nf">addColumns</span><span class="p">(</span><span class="nx">win</span><span class="p">);</span>
<span class="p">},</span>

<span class="nf">destroy</span><span class="p">()</span> <span class="p">{</span>
<span class="k">this</span><span class="p">.</span><span class="nf">destroyColumns</span><span class="p">();</span>
<span class="p">},</span>

<span class="nf">observe</span><span class="p">(</span><span class="nx">aMsgFolder</span><span class="p">,</span> <span class="nx">aTopic</span><span class="p">,</span> <span class="nx">aData</span><span class="p">)</span> <span class="p">{</span>
<span class="k">try</span> <span class="p">{</span>
<span class="nx">jalaliDateColumnHandler</span><span class="p">.</span><span class="nf">init</span><span class="p">(</span><span class="k">this</span><span class="p">.</span><span class="nx">win</span><span class="p">);</span>
<span class="k">this</span><span class="p">.</span><span class="nx">win</span><span class="p">.</span><span class="nx">gDBView</span><span class="p">.</span><span class="nf">addColumnHandler</span><span class="p">(</span>
<span class="dl">"</span><span class="s2">jalaliDateColumn</span><span class="dl">"</span><span class="p">,</span>
<span class="nx">jalaliDateColumnHandler</span>
<span class="p">);</span>
<span class="p">}</span> <span class="k">catch </span><span class="p">(</span><span class="nx">ex</span><span class="p">)</span> <span class="p">{</span>
<span class="nx">console</span><span class="p">.</span><span class="nf">error</span><span class="p">(</span><span class="nx">ex</span><span class="p">);</span>
<span class="k">throw</span> <span class="k">new</span> <span class="nc">Error</span><span class="p">(</span><span class="dl">"</span><span class="s2">Cannot add column handler</span><span class="dl">"</span><span class="p">);</span>
<span class="p">}</span>
<span class="p">},</span>

<span class="nf">addColumn</span><span class="p">(</span><span class="nx">win</span><span class="p">,</span> <span class="nx">columnId</span><span class="p">,</span> <span class="nx">columnLabel</span><span class="p">)</span> <span class="p">{</span>
<span class="k">if </span><span class="p">(</span><span class="nx">win</span><span class="p">.</span><span class="nb">document</span><span class="p">.</span><span class="nf">getElementById</span><span class="p">(</span><span class="nx">columnId</span><span class="p">))</span> <span class="k">return</span><span class="p">;</span>

    <span class="kd">const</span> <span class="nx">treeCol</span> <span class="o">=</span> <span class="nx">win</span><span class="p">.</span><span class="nb">document</span><span class="p">.</span><span class="nf">createXULElement</span><span class="p">(</span><span class="dl">"</span><span class="s2">treecol</span><span class="dl">"</span><span class="p">);</span>
    <span class="nx">treeCol</span><span class="p">.</span><span class="nf">setAttribute</span><span class="p">(</span><span class="dl">"</span><span class="s2">id</span><span class="dl">"</span><span class="p">,</span> <span class="nx">columnId</span><span class="p">);</span>
    <span class="nx">treeCol</span><span class="p">.</span><span class="nf">setAttribute</span><span class="p">(</span><span class="dl">"</span><span class="s2">persist</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">hidden ordinal sortDirection width</span><span class="dl">"</span><span class="p">);</span>
    <span class="nx">treeCol</span><span class="p">.</span><span class="nf">setAttribute</span><span class="p">(</span><span class="dl">"</span><span class="s2">flex</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">2</span><span class="dl">"</span><span class="p">);</span>
    <span class="nx">treeCol</span><span class="p">.</span><span class="nf">setAttribute</span><span class="p">(</span><span class="dl">"</span><span class="s2">closemenu</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">none</span><span class="dl">"</span><span class="p">);</span>
    <span class="nx">treeCol</span><span class="p">.</span><span class="nf">setAttribute</span><span class="p">(</span><span class="dl">"</span><span class="s2">label</span><span class="dl">"</span><span class="p">,</span> <span class="nx">columnLabel</span><span class="p">);</span>
    <span class="nx">treeCol</span><span class="p">.</span><span class="nf">setAttribute</span><span class="p">(</span><span class="dl">"</span><span class="s2">tooltiptext</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">Solar Date</span><span class="dl">"</span><span class="p">);</span>

    <span class="kd">const</span> <span class="nx">threadCols</span> <span class="o">=</span> <span class="nx">win</span><span class="p">.</span><span class="nb">document</span><span class="p">.</span><span class="nf">getElementById</span><span class="p">(</span><span class="dl">"</span><span class="s2">threadCols</span><span class="dl">"</span><span class="p">);</span>
    <span class="nx">threadCols</span><span class="p">.</span><span class="nf">appendChild</span><span class="p">(</span><span class="nx">treeCol</span><span class="p">);</span>

    <span class="c1">// Restore persisted attributes.</span>
    <span class="kd">let</span> <span class="nx">attributes</span> <span class="o">=</span> <span class="nx">Services</span><span class="p">.</span><span class="nx">xulStore</span><span class="p">.</span><span class="nf">getAttributeEnumerator</span><span class="p">(</span>
      <span class="k">this</span><span class="p">.</span><span class="nx">win</span><span class="p">.</span><span class="nb">document</span><span class="p">.</span><span class="nx">URL</span><span class="p">,</span>
      <span class="nx">columnId</span>
    <span class="p">);</span>
    <span class="k">for </span><span class="p">(</span><span class="kd">let</span> <span class="nx">attribute</span> <span class="k">of</span> <span class="nx">attributes</span><span class="p">)</span> <span class="p">{</span>
      <span class="kd">let</span> <span class="nx">value</span> <span class="o">=</span> <span class="nx">Services</span><span class="p">.</span><span class="nx">xulStore</span><span class="p">.</span><span class="nf">getValue</span><span class="p">(</span>
        <span class="k">this</span><span class="p">.</span><span class="nx">win</span><span class="p">.</span><span class="nb">document</span><span class="p">.</span><span class="nx">URL</span><span class="p">,</span>
        <span class="nx">columnId</span><span class="p">,</span>
        <span class="nx">attribute</span>
      <span class="p">);</span>
      <span class="k">if </span><span class="p">(</span>
        <span class="nx">attribute</span> <span class="o">!=</span> <span class="dl">"</span><span class="s2">ordinal</span><span class="dl">"</span> <span class="o">||</span>
        <span class="nf">parseInt</span><span class="p">(</span><span class="nx">AppConstants</span><span class="p">.</span><span class="nx">MOZ_APP_VERSION</span><span class="p">,</span> <span class="mi">10</span><span class="p">)</span> <span class="o">&lt;</span> <span class="mi">74</span>
      <span class="p">)</span> <span class="p">{</span>
        <span class="nx">treeCol</span><span class="p">.</span><span class="nf">setAttribute</span><span class="p">(</span><span class="nx">attribute</span><span class="p">,</span> <span class="nx">value</span><span class="p">);</span>
      <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
        <span class="nx">treeCol</span><span class="p">.</span><span class="nx">ordinal</span> <span class="o">=</span> <span class="nx">value</span><span class="p">;</span>
      <span class="p">}</span>
    <span class="p">}</span>

    <span class="nx">Services</span><span class="p">.</span><span class="nx">obs</span><span class="p">.</span><span class="nf">addObserver</span><span class="p">(</span><span class="k">this</span><span class="p">,</span> <span class="dl">"</span><span class="s2">MsgCreateDBView</span><span class="dl">"</span><span class="p">,</span> <span class="kc">false</span><span class="p">);</span>

<span class="p">},</span>

<span class="nf">addColumns</span><span class="p">(</span><span class="nx">win</span><span class="p">)</span> <span class="p">{</span>
<span class="k">this</span><span class="p">.</span><span class="nf">addColumn</span><span class="p">(</span><span class="nx">win</span><span class="p">,</span> <span class="dl">"</span><span class="s2">jalaliDateColumn</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">Date</span><span class="dl">"</span><span class="p">);</span>
<span class="p">},</span>

<span class="nf">destroyColumn</span><span class="p">(</span><span class="nx">columnId</span><span class="p">)</span> <span class="p">{</span>
<span class="kd">const</span> <span class="nx">treeCol</span> <span class="o">=</span> <span class="k">this</span><span class="p">.</span><span class="nx">win</span><span class="p">.</span><span class="nb">document</span><span class="p">.</span><span class="nf">getElementById</span><span class="p">(</span><span class="nx">columnId</span><span class="p">);</span>
<span class="k">if </span><span class="p">(</span><span class="o">!</span><span class="nx">treeCol</span><span class="p">)</span> <span class="k">return</span><span class="p">;</span>
<span class="nx">treeCol</span><span class="p">.</span><span class="nf">remove</span><span class="p">();</span>
<span class="p">},</span>

<span class="nf">destroyColumns</span><span class="p">()</span> <span class="p">{</span>
<span class="k">this</span><span class="p">.</span><span class="nf">destroyColumn</span><span class="p">(</span><span class="dl">"</span><span class="s2">jalaliDateColumn</span><span class="dl">"</span><span class="p">);</span>
<span class="nx">Services</span><span class="p">.</span><span class="nx">obs</span><span class="p">.</span><span class="nf">removeObserver</span><span class="p">(</span><span class="k">this</span><span class="p">,</span> <span class="dl">"</span><span class="s2">MsgCreateDBView</span><span class="dl">"</span><span class="p">);</span>
<span class="p">},</span>
<span class="p">};</span>

<span class="kd">var</span> <span class="nx">MahourDateHeaderView</span> <span class="o">=</span> <span class="p">{</span>
<span class="nf">init</span><span class="p">(</span><span class="nx">win</span><span class="p">)</span> <span class="p">{</span>
<span class="k">this</span><span class="p">.</span><span class="nx">win</span> <span class="o">=</span> <span class="nx">win</span><span class="p">;</span>
<span class="nx">columnOverlay</span><span class="p">.</span><span class="nf">init</span><span class="p">(</span><span class="nx">win</span><span class="p">);</span>

    <span class="k">if </span><span class="p">(</span>
      <span class="nx">win</span><span class="p">.</span><span class="nx">gDBView</span> <span class="o">&amp;&amp;</span>
      <span class="nx">win</span><span class="p">.</span><span class="nb">document</span><span class="p">.</span><span class="nx">documentElement</span><span class="p">.</span><span class="nf">getAttribute</span><span class="p">(</span><span class="dl">"</span><span class="s2">windowtype</span><span class="dl">"</span><span class="p">)</span> <span class="o">==</span> <span class="dl">"</span><span class="s2">mail:3pane</span><span class="dl">"</span>
    <span class="p">)</span> <span class="p">{</span>
      <span class="nx">Services</span><span class="p">.</span><span class="nx">obs</span><span class="p">.</span><span class="nf">notifyObservers</span><span class="p">(</span><span class="kc">null</span><span class="p">,</span> <span class="dl">"</span><span class="s2">MsgCreateDBView</span><span class="dl">"</span><span class="p">);</span>
    <span class="p">}</span>

<span class="p">},</span>

<span class="nf">destroy</span><span class="p">()</span> <span class="p">{</span>
<span class="nx">columnOverlay</span><span class="p">.</span><span class="nf">destroy</span><span class="p">();</span>
<span class="p">},</span>
<span class="p">};</span></code></pre></figure>


</div>

<p>The main content of the column is built here and the new column is created.</p>

<h3 id="background-script">Background Script</h3>

<p>Background scripts, as special pages, have the <a href="https://developer.mozilla.org/en-US/docs/Web/API/Window" target="\_blank" rel="noopener noreferrer"><code class="language-plaintext highlighter-rouge">window</code></a> property with the <a href="https://developer.mozilla.org/en-US/docs/Glossary/DOM" target="\_blank" rel="noopener noreferrer"><code class="language-plaintext highlighter-rouge">Document Object Model</code></a> property.</p>

<p>Background page properties:</p>

<p>The background page can access <code class="language-plaintext highlighter-rouge">WebExtension APIs</code> when the extension has the necessary permissions. It can also send <code class="language-plaintext highlighter-rouge">XHR</code> requests based on host permissions (<a href="https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/manifest.json/permissions" target="\_blank" rel="noopener noreferrer"><code class="language-plaintext highlighter-rouge">host permissions</code></a>). For more information, search for <code class="language-plaintext highlighter-rouge">Cross-origin access</code>.</p>

<p>The background script does not have direct access to web pages, but it can load content scripts (<a href="https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/Content_scripts" target="\_blank" rel="noopener noreferrer"><code class="language-plaintext highlighter-rouge">content scripts</code></a>) in them and communicate with them using the <a href="https://developer.mozilla.org/en-US/Add-ons/WebExtensions/Content_scripts#Communicating_with_background_scripts" target="\_blank" rel="noopener noreferrer"><code class="language-plaintext highlighter-rouge">message-passing API</code></a>. This approach is used in the Mahour extension.</p>

<blockquote>
  <p>Note: Background scripts also have limitations to prevent access to unauthorized actions. The inability to call <code class="language-plaintext highlighter-rouge">eval()</code> is an example of these limitations.
For more information, read <a href="https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/Content_Security_Policy" target="\_blank" rel="noopener noreferrer"><code class="language-plaintext highlighter-rouge">Content Security Policy</code></a>.
Also, calling <code class="language-plaintext highlighter-rouge">alert()</code>, <code class="language-plaintext highlighter-rouge">confirm()</code>, or <code class="language-plaintext highlighter-rouge">prompt()</code> is not possible in this page.</p>
</blockquote>

<p>Mahour’s background script code:</p>

<div class="code-block">

<figure class="highlight"><pre><code class="language-javascript" data-lang="javascript"><span class="dl">"</span><span class="s2">use strict</span><span class="dl">"</span><span class="p">;</span>

<span class="cm">/*
Default settings. Initialize storage to these values.
*/</span>
<span class="kd">var</span> <span class="nx">datePrefrences</span> <span class="o">=</span> <span class="p">{</span>
<span class="na">longMonth</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span>
<span class="na">showTime</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span>
<span class="na">weekDay</span><span class="p">:</span> <span class="kc">false</span><span class="p">,</span>
<span class="na">englishNumbers</span><span class="p">:</span> <span class="kc">false</span><span class="p">,</span>
<span class="p">};</span>

<span class="cm">/*
Generic error logger.
*/</span>
<span class="kd">function</span> <span class="nf">onError</span><span class="p">(</span><span class="nx">e</span><span class="p">)</span> <span class="p">{</span>
<span class="nx">console</span><span class="p">.</span><span class="nf">error</span><span class="p">(</span><span class="nx">e</span><span class="p">);</span>
<span class="p">}</span>

<span class="cm">/*
On startup, check whether we have stored settings.
If we don't, then store the default settings.
*/</span>
<span class="kd">function</span> <span class="nf">checkStoredSettings</span><span class="p">(</span><span class="nx">storedSettings</span><span class="p">)</span> <span class="p">{</span>
<span class="k">if </span><span class="p">(</span><span class="o">!</span><span class="nx">storedSettings</span><span class="p">.</span><span class="nx">datePrefrences</span><span class="p">)</span> <span class="p">{</span>
<span class="nx">browser</span><span class="p">.</span><span class="nx">storage</span><span class="p">.</span><span class="nx">local</span><span class="p">.</span><span class="nf">set</span><span class="p">({</span> <span class="nx">datePrefrences</span> <span class="p">});</span>
<span class="p">}</span>
<span class="p">}</span>

<span class="kd">function</span> <span class="nf">repaint</span><span class="p">(</span><span class="nx">newSettings</span><span class="p">)</span> <span class="p">{</span>
<span class="nx">browser</span><span class="p">.</span><span class="nx">MahourDate</span><span class="p">.</span><span class="nf">changeSettings</span><span class="p">(</span><span class="nx">newSettings</span><span class="p">);</span>
<span class="p">}</span>

<span class="kd">const</span> <span class="nx">gettingStoredSettings</span> <span class="o">=</span> <span class="nx">browser</span><span class="p">.</span><span class="nx">storage</span><span class="p">.</span><span class="nx">local</span><span class="p">.</span><span class="nf">get</span><span class="p">();</span>
<span class="nx">gettingStoredSettings</span><span class="p">.</span><span class="nf">then</span><span class="p">(</span><span class="nx">checkStoredSettings</span><span class="p">,</span> <span class="nx">onError</span><span class="p">);</span>

<span class="cm">/* globals browser */</span>
<span class="kd">var</span> <span class="nx">init</span> <span class="o">=</span> <span class="k">async </span><span class="p">()</span> <span class="o">=&gt;</span> <span class="p">{</span>
<span class="nx">browser</span><span class="p">.</span><span class="nx">MahourDate</span><span class="p">.</span><span class="nf">addWindowListener</span><span class="p">(</span><span class="dl">"</span><span class="s2">hich</span><span class="dl">"</span><span class="p">);</span>
<span class="p">};</span>

<span class="nf">init</span><span class="p">();</span></code></pre></figure>


</div>

<p>The <code class="language-plaintext highlighter-rouge">storage</code> permission is obtained in <code class="language-plaintext highlighter-rouge">manifest.json</code> to use <code class="language-plaintext highlighter-rouge">browser.storage.local.get()</code> and <code class="language-plaintext highlighter-rouge">browser.storage.local.set()</code>. <code class="language-plaintext highlighter-rouge">MahourDate</code> is the identifier for this extension’s new API. Using <code class="language-plaintext highlighter-rouge">browser.MahourDate.changeSettings(newSettings)</code>, one of its functions is called to apply settings.</p>

<h1 id="update-for-thunderbird-version-115-and-later">Update for <code class="language-plaintext highlighter-rouge">Thunderbird</code> Version 115 and Later</h1>

<p>In Thunderbird version 110, support for <code class="language-plaintext highlighter-rouge">Custom Column</code> was discontinued and the Mahour extension stopped working. With the addition of a new <code class="language-plaintext highlighter-rouge">API</code> in version 115, it became possible to update the code and support these versions. If you’d like to know more, we discussed it <a href="https://github.com/mhdzli/mahoor/issues/8">here</a>.</p>

<p>Since I didn’t have enough time to update the code, it took a while, but eventually Mahour supports newer versions up to 128.</p>

<h1 id="useful-resources">Useful Resources:</h1>

<p>If you’re not familiar with building extensions and are just starting out, watch this video by Shojaei:</p>

<div class="video">
  <iframe sandbox="allow-same-origin allow-scripts allow-popups" src="https://peertube.linuxrocks.online/videos/embed/d70182ca-9ea9-4411-bd24-86dcbff95ceb" frameborder="0" allowfullscreen=""></iframe>
</div>

<p>To test extensions on Thunderbird, in the <code class="language-plaintext highlighter-rouge">Add-ons Manager</code> page click on <code class="language-plaintext highlighter-rouge">Tools for all add-ons</code> (<i class="fa fa-cog"></i> <i class="fa fa-chevron-down"></i>). By selecting <code class="language-plaintext highlighter-rouge">Debug Add-ons</code>, a new tab opens where you can temporarily install and test extensions. Simply click on <code class="language-plaintext highlighter-rouge">Load Temporary Add-on</code> and select the extension’s <code class="language-plaintext highlighter-rouge">manifest.json</code> file.</p>

<ul>
  <li><a href="https://firefox-source-docs.mozilla.org/toolkit/components/extensions/webextensions/index.html" target="\_blank" rel="noopener noreferrer">Extension API development guide</a></li>
  <li><a href="https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions" target="\_blank" rel="noopener noreferrer">Browser extensions</a></li>
  <li><a href="https://github.com/mdn/webextensions-examples" target="\_blank" rel="noopener noreferrer">Simple examples</a></li>
</ul>]]></content><author><name>محمد زینلی</name><email>m@zmim.ir</email></author><category term="unix/linux" /><category term="foss" /><category term="my-projects" /><summary type="html"><![CDATA[Mahour is an extension for adding a Solar Hijri date column to the popular Thunderbird email client.]]></summary></entry></feed>