<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss xmlns:yandex="http://news.yandex.ru" xmlns:media="http://search.yahoo.com/mrss/" xmlns:turbo="http://turbo.yandex.ru" version="2.0"><channel><title>TenderOwl</title><link>https://tenderowl.com/</link><description>Recent content on TenderOwl</description><generator>Hugo -- gohugo.io</generator><language>en</language><lastBuildDate>Thu, 25 Sep 2025 11:50:38 +0200</lastBuildDate><atom:link href="https://tenderowl.com/index.xml" rel="self" type="application/rss+xml"/><item turbo="true"><turbo:extendedHtml>true</turbo:extendedHtml><title>A Template for Building .NET GNOME Applications with Flatpak</title><link>https://tenderowl.com/posts/gircore-gnome-flatpak-template/</link><pubDate>Thu, 25 Sep 2025 11:50:38 +0200</pubDate><guid>https://tenderowl.com/posts/gircore-gnome-flatpak-template/</guid><description>&lt;h2 id="introduction">Introduction&lt;/h2>
&lt;p>Developing native applications for the GNOME platform using .NET has become significantly easier. I&amp;rsquo;m excited to introduce &lt;strong>TenderOwl.GirCoreTemplate.CSharp&lt;/strong>, a project template that automates creating applications using &lt;strong>Gir.Core&lt;/strong> bindings, the modern &lt;strong>Blueprint&lt;/strong> interface markup language, and &lt;strong>Flatpak&lt;/strong> packaging.&lt;/p>
&lt;p>My journey with C# began back in 2003, and when the Gir.Core project emerged—a binding generator for GTK4 and GObject-based libraries—I couldn&amp;rsquo;t resist getting involved. Over time, I developed the idea to create a template that would streamline getting started with these technologies while enabling the use of familiar tools like GNOME Builder. This template is the result of that work.&lt;/p></description><turbo:content><![CDATA[
            <h2 id="introduction">Introduction</h2>
<p>Developing native applications for the GNOME platform using .NET has become significantly easier. I&rsquo;m excited to introduce <strong>TenderOwl.GirCoreTemplate.CSharp</strong>, a project template that automates creating applications using <strong>Gir.Core</strong> bindings, the modern <strong>Blueprint</strong> interface markup language, and <strong>Flatpak</strong> packaging.</p>
<p>My journey with C# began back in 2003, and when the Gir.Core project emerged—a binding generator for GTK4 and GObject-based libraries—I couldn&rsquo;t resist getting involved. Over time, I developed the idea to create a template that would streamline getting started with these technologies while enabling the use of familiar tools like GNOME Builder. This template is the result of that work.</p>
<h2 id="prerequisites">Prerequisites</h2>
<p>Before getting started, ensure you have the following software installed:</p>
<ul>
<li><a href="https://dotnet.microsoft.com/"><strong>.NET SDK 9.0</strong></a> or newer</li>
<li><a href="https://gnome.pages.gitlab.gnome.org/blueprint-compiler/"><strong>Blueprint Compiler</strong></a></li>
<li><a href="https://flatpak.org/"><strong>Flatpak</strong></a> with GNOME runtime (for building and testing)</li>
<li><a href="https://apps.gnome.org/Builder/"><strong>GNOME Builder</strong></a> (recommended for development)</li>
</ul>
<h2 id="template-installation-and-project-creation">Template Installation and Project Creation</h2>
<h3 id="installing-the-template">Installing the Template</h3>
<p>The template is available via NuGet and can be installed with a single command:</p>
<div class="rounded"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">dotnet new install TenderOwl.GirCoreTemplate.CSharp
</span></span></code></pre></div><h3 id="creating-a-new-project">Creating a New Project</h3>
<p>Create a project from the template by specifying a unique application ID (replace <code>YOUR_APP_ID</code> with your identifier, such as <code>io.github.yourname.MyApp</code>):</p>
<div class="rounded"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">dotnet new gnome-gircore --app-id YOUR_APP_ID -n MyGnomeApp -o MyGnomeApp
</span></span></code></pre></div><blockquote>
<p><strong>Important:</strong> The application ID must be unique and follow the <a href="https://docs.flatpak.org/en/latest/conventions.html#application-ids">Reverse DNS standard</a>.</p>
</blockquote>
<h2 id="project-structure">Project Structure</h2>
<p>After creating the project, you&rsquo;ll see the following file structure:</p>
<div class="rounded"><pre tabindex="0" class="chroma"><code class="language-zed" data-lang="zed"><span class="line"><span class="cl"><span class="n">your</span><span class="o">-</span><span class="nn">application/</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="err">├──</span><span class="w"> </span><span class="nn">MyApp/</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="err">│</span><span class="w">   </span><span class="err">├──</span><span class="w"> </span><span class="n">Program</span><span class="p">.</span><span class="n">cs</span><span class="w">                     </span><span class="err">#</span><span class="w"> </span><span class="n">Application</span><span class="w"> </span><span class="n">entry</span><span class="w"> </span><span class="n">point</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="err">│</span><span class="w">   </span><span class="err">├──</span><span class="w"> </span><span class="n">Constants</span><span class="p">.</span><span class="n">cs</span><span class="w">                   </span><span class="err">#</span><span class="w"> </span><span class="n">Constants</span><span class="w"> </span><span class="n">including</span><span class="w"> </span><span class="n">application</span><span class="w"> </span><span class="n">ID</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="err">│</span><span class="w">   </span><span class="err">└──</span><span class="w"> </span><span class="nn">ui/</span><span class="w">                            </span><span class="err">#</span><span class="w"> </span><span class="n">User</span><span class="w"> </span><span class="n">interface</span><span class="w"> </span><span class="n">classes</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="err">│</span><span class="w">       </span><span class="err">└──</span><span class="w"> </span><span class="n">MainWindow</span><span class="p">.</span><span class="n">cs</span><span class="w">              </span><span class="err">#</span><span class="w"> </span><span class="n">Main</span><span class="w"> </span><span class="n">application</span><span class="w"> </span><span class="n">window</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="err">├──</span><span class="w"> </span><span class="nn">data/</span><span class="w">                              </span><span class="err">#</span><span class="w"> </span><span class="n">Data</span><span class="w"> </span><span class="n">and</span><span class="w"> </span><span class="n">resource</span><span class="w"> </span><span class="n">files</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="err">│</span><span class="w">   </span><span class="err">├──</span><span class="w"> </span><span class="nn">icons/</span><span class="w">                         </span><span class="err">#</span><span class="w"> </span><span class="n">Application</span><span class="w"> </span><span class="n">icons</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="err">│</span><span class="w">   </span><span class="err">├──</span><span class="w"> </span><span class="nn">ui/</span><span class="w">                            </span><span class="err">#</span><span class="w"> </span><span class="n">Blueprint</span><span class="w"> </span><span class="n">files</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="err">│</span><span class="w">   </span><span class="err">│</span><span class="w">   </span><span class="err">└──</span><span class="w"> </span><span class="n">MainWindow</span><span class="p">.</span><span class="n">blp</span><span class="w">             </span><span class="err">#</span><span class="w"> </span><span class="n">Main</span><span class="w"> </span><span class="n">window</span><span class="w"> </span><span class="n">layout</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="err">│</span><span class="w">   </span><span class="err">├──</span><span class="w"> </span><span class="n">__APP_ID__</span><span class="p">.</span><span class="n">desktop</span><span class="w">             </span><span class="err">#</span><span class="w"> </span><span class="n">Desktop</span><span class="w"> </span><span class="n">integration</span><span class="w"> </span><span class="n">file</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="err">│</span><span class="w">   </span><span class="err">├──</span><span class="w"> </span><span class="n">__APP_ID__</span><span class="p">.</span><span class="n">appdata</span><span class="p">.</span><span class="n">xml</span><span class="w">         </span><span class="err">#</span><span class="w"> </span><span class="n">Application</span><span class="w"> </span><span class="n">metadata</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="err">│</span><span class="w">   </span><span class="err">├──</span><span class="w"> </span><span class="n">__APP_ID__</span><span class="p">.</span><span class="n">gresource</span><span class="p">.</span><span class="n">xml</span><span class="w">       </span><span class="err">#</span><span class="w"> </span><span class="n">Resource</span><span class="w"> </span><span class="kt">definition</span><span class="w"> </span><span class="n">file</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="err">│</span><span class="w">   </span><span class="err">└──</span><span class="w"> </span><span class="n">__APP_ID__</span><span class="p">.</span><span class="n">gschema</span><span class="p">.</span><span class="n">xml</span><span class="w">         </span><span class="err">#</span><span class="w"> </span><span class="n">GSettings</span><span class="w"> </span><span class="n">schema</span><span class="w"> </span><span class="kt">definition</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="err">├──</span><span class="w"> </span><span class="n">build</span><span class="o">-</span><span class="nn">aux/</span><span class="w">                         </span><span class="err">#</span><span class="w"> </span><span class="n">Build</span><span class="w"> </span><span class="n">auxiliary</span><span class="w"> </span><span class="n">files</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="err">│</span><span class="w">   </span><span class="err">├──</span><span class="w"> </span><span class="n">__APP_ID__</span><span class="p">.</span><span class="n">yaml</span><span class="w">                </span><span class="err">#</span><span class="w"> </span><span class="n">Flatpak</span><span class="w"> </span><span class="n">manifest</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="err">│</span><span class="w">   </span><span class="err">└──</span><span class="w"> </span><span class="n">nuget</span><span class="o">-</span><span class="n">sources</span><span class="p">.</span><span class="n">json</span><span class="w">             </span><span class="err">#</span><span class="w"> </span><span class="n">NuGet</span><span class="w"> </span><span class="n">sources</span><span class="w"> </span><span class="n">configuration</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="err">└──</span><span class="w"> </span><span class="nn">build/</span><span class="w">                             </span><span class="err">#</span><span class="w"> </span><span class="n">Generated</span><span class="w"> </span><span class="n">files</span><span class="w"> </span><span class="p">(</span><span class="n">created</span><span class="w"> </span><span class="n">during</span><span class="w"> </span><span class="n">build</span><span class="p">)</span><span class="w">
</span></span></span></code></pre></div><h3 id="key-project-files">Key Project Files</h3>
<ul>
<li><strong><code>Constants.cs</code></strong> — Contains application constants including <code>APP_ID</code>. When changing the ID, update it in all files using <code>__APP_ID__</code>.</li>
<li><strong><code>build-aux/__APP_ID__.yaml</code></strong> — Flatpak manifest defining dependencies, permissions, and build stages.</li>
<li><strong><code>data/ui/MainWindow.blp</code></strong> and <strong><code>ui/MainWindow.cs</code></strong> — UI layout and application logic pairing.</li>
<li><strong><code>__APP_ID__.appdata.xml</code></strong> — Metadata for GNOME Software Center.</li>
<li><strong><code>__APP_ID__.gschema.xml</code></strong> — Application settings schema.</li>
<li><strong><code>__APP_ID__.gresource.xml</code></strong> — Application resource definition.</li>
</ul>
<h2 id="building-and-running">Building and Running</h2>
<h3 id="development-with-gnome-builder-recommended">Development with GNOME Builder (Recommended)</h3>
<ol>
<li>Open the project folder in GNOME Builder</li>
<li>The IDE will automatically recognize the project</li>
<li>Click &ldquo;Run&rdquo; to build and launch the application in the required environment</li>
</ol>
<h3 id="building-with-flatpak-builder">Building with flatpak-builder</h3>
<p>To build and install the application manually, run these commands from the project root directory (replacing <code>__APP_ID__</code> with your identifier):</p>
<div class="rounded"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">flatpak-builder --user --install build-dir build-aux/__APP_ID__.yaml --force-clean
</span></span></code></pre></div><p>After installation, the application will appear in your menu and can be launched with:</p>
<div class="rounded"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">flatpak run YOUR_APP_ID
</span></span></code></pre></div><blockquote>
<p><strong>Important:</strong> Running via <code>dotnet run</code> won&rsquo;t work as the application depends on GNOME-specific features (desktop files, GSettings, resources, etc.).</p>
</blockquote>
<h2 id="build-process">Build Process</h2>
<p>The build process is detailed in the application&rsquo;s Flatpak manifest (<code>build-aux/__APP_ID__.yaml</code>):</p>
<ol>
<li>Blueprint Compiler installation</li>
<li>.NET SDK and runtime setup</li>
<li>Application build including:
<ul>
<li>C# code compilation</li>
<li>Blueprint file compilation into UI resources</li>
<li>Resource compilation using <code>gnome-compile-resources</code></li>
<li>Application metadata copying</li>
<li>GSettings schema compilation</li>
</ul>
</li>
</ol>
<blockquote>
<p><strong>Note:</strong> Any additional files required for your application must be manually added to the Flatpak manifest.</p>
</blockquote>
<h2 id="working-with-blueprint-files">Working with Blueprint Files</h2>
<p>Blueprint is a modern replacement for traditional XML/.ui files for describing interfaces in GNOME. When working with them:</p>
<ul>
<li>Files must be located in the <code>data/ui/</code> directory</li>
<li>Use the <code>.blp</code> extension—files with other extensions are ignored by the build system</li>
<li>For example, <code>MainWindow.blp</code> will be processed, while <code>MainWindow.foo</code> will be ignored</li>
</ul>
<h2 id="next-steps">Next Steps</h2>
<p>After creating your project, you can:</p>
<ol>
<li><strong>Modify the interface</strong> — Edit files in the <code>data/ui/</code> directory. Find official Blueprint documentation on the <a href="https://jwestman.pages.gitlab.gnome.org/blueprint-compiler/">project website</a>.</li>
<li><strong>Add logic</strong> — Work with code in the <code>ui/</code> directory. Study the <a href="https://github.com/gircore/gir.core">Gir.Core documentation</a> to understand available GTK APIs.</li>
<li><strong>Configure the manifest</strong> — Open <code>build-aux/__APP_ID__.yaml</code> to add your application&rsquo;s dependencies (e.g., for network or database functionality).</li>
<li><strong>Add icons</strong> — Place your icons in the <code>data/icons/</code> directory in appropriate sizes.</li>
</ol>
<h2 id="conclusion">Conclusion</h2>
<p>The TenderOwl.GirCoreTemplate.CSharp template significantly simplifies starting .NET application development for GNOME by handling all the complexity of initial build and deployment configuration. It allows you to focus on creating application functionality rather than tool configuration.</p>
<p>If you have questions or suggestions for improving the template, I&rsquo;d be happy to discuss them in the <a href="https://github.com/gircore/gir.core/issues/938">corresponding issue</a> of the Gir.Core repository.</p>
<p>Happy coding!</p>

        ]]></turbo:content></item><item turbo="true"><turbo:extendedHtml>true</turbo:extendedHtml><title>Attemp to build Norka 2</title><link>https://tenderowl.com/posts/attempt-to-build-norka-2/</link><pubDate>Thu, 10 Jul 2025 17:55:38 +0300</pubDate><guid>https://tenderowl.com/posts/attempt-to-build-norka-2/</guid><description>&lt;h2 id="introduction">Introduction&lt;/h2>
&lt;p>For more than a year now, I have been periodically returning to trying to rethink my note-taking application, &lt;a href="https://flathub.org/apps/com.github.tenderowl.norka">Norka&lt;/a>, to update the interface, add modern features, and finally add synchronization with the cloud.&lt;/p>
&lt;p>I&amp;rsquo;ve had several attempts to use Rust to add speed and reliability to the application. I use C# (&lt;a href="https://gircore.github.io/">Gir.Core&lt;/a>) for the same reasons and because I love this language. But unfortunately, in the end I returned to Python, because it and &lt;a href="https://pygobject.gnome.org/">PyGObject&lt;/a> provide the necessary development speed and support a lot of libraries that I need for my work. The Python development ecosystem for GTK looks the most mature and complete.&lt;/p></description><turbo:content><![CDATA[
            <h2 id="introduction">Introduction</h2>
<p>For more than a year now, I have been periodically returning to trying to rethink my note-taking application, <a href="https://flathub.org/apps/com.github.tenderowl.norka">Norka</a>, to update the interface, add modern features, and finally add synchronization with the cloud.</p>
<p>I&rsquo;ve had several attempts to use Rust to add speed and reliability to the application. I use C# (<a href="https://gircore.github.io/">Gir.Core</a>) for the same reasons and because I love this language. But unfortunately, in the end I returned to Python, because it and <a href="https://pygobject.gnome.org/">PyGObject</a> provide the necessary development speed and support a lot of libraries that I need for my work. The Python development ecosystem for GTK looks the most mature and complete.</p>
<p>So, after a year of experimentation and prototyping, studying analogs and my application requirements, I started another attempt to implement Norka 2.</p>
<h2 id="inspiration">Inspiration</h2>
<p>I have been working with macOS for more than 10 years and I believe that it is for this OS that the most beautiful and user-friendly applications have been created from the point of view of UX. Let&rsquo;s leave beauty out of the box, as it&rsquo;s a matter of taste, and turn to user experience.</p>
<p>I have tried working with many applications, especially considering those that have received awards for their design - Craft, Ulysses, Bear, Evernote, IA Writer, etc. I also looked at various online services such as Microsoft Loop, Confluence (and why not?) and others . At some point in time, I caught myself thinking that 2 services had become the most convenient for me: craft.do as a desktop application and web service, and Microsoft Loop as a web service. It was decided to assemble the functionality of the new version of Norka from them, namely:</p>
<ul>
<li>Workspaces</li>
<li>A tree structure where each page can have nested pages.</li>
<li>Customization of the design of pages and spaces: icons and covers help with work and just make work more fun.</li>
<li>Instead of Markdown, use rich formatting in the document itself, like office software.</li>
<li>The ability to publish pages, share them with other users, or make them publicly available to everyone.</li>
<li>Sync with the cloud.</li>
<li>Encryption of spaces and pages with password access for published pages.</li>
<li>Integration with LLM both locally and externally (whether <a href="https://ollama.com/">Ollama</a> or OpenAI)</li>
<li>The ability to create tasks, as implemented in Craft.</li>
<li>Advanced search capabilities, calendar linking, etc.</li>
</ul>
<p>Of course, this is not the final list, and it will be updated. However, it&rsquo;s a great set to start with :)</p>
<p>By the way, you can follow the updates on my <a href="https://x.com/meamka">Twitter</a> or on the project&rsquo;s <a href="https://github.com/TenderOwl/NorkaXT">Github</a>.</p>
<h2 id="workspaces">Workspaces</h2>
<p>The first thing to do was to sort out the workspaces. The task they are designed to solve is to group pages by topic, project, or otherwise. The user can decide for himself, the main thing is to give him the opportunity. And at the same time, make it possible to add an icon, cover, and control functionality. So we get something like this:</p>
<p><img src="https://dev-to-uploads.s3.amazonaws.com/uploads/articles/w67etd46k6xuvkohbebf.png" alt="Workspace creation dialog"></p>
<p>The user can select an emoji icon and a cover from the preset images. However, in the future I plan to add integration with online services such as <a href="https://www.pexels.com/">Pexels</a> or <a href="https://unsplash.com/">Unsplash</a>.</p>
<p>Workspaces contain pages that are deleted when the space is deleted. So be careful 🙂</p>
<h2 id="pages">Pages</h2>
<p>If workspaces are complementary functionality, then creating and editing text is the main one. Therefore, it will require the most work on itself, but let&rsquo;s see what has already been implemented.</p>
<h3 id="editing">Editing</h3>
<p>Surprisingly, the fact is that pages can already be created and their contents edited. At the same time, syntax highlighting is now being used, which migrated from the first version of Norka - Markdown markup. In addition to this, I started implementing work with formatting tags, without having to use a markup language. My experience suggests that this is a more convenient option, especially if you combine it with Markdown - use markup during typing as shortcuts for tags. For example, if a user types</p>
<div class="rounded"><pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="cl"># The Awesome Title
</span></span></code></pre></div><p>, then this text will be automatically converted to a title, and the # sign at the beginning of the line will be removed like this:</p>
<div class="rounded"><pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="cl">The Awesome Title
</span></span></code></pre></div><p>This should work for titles, links, images, and other markup elements.</p>
<p>And if the ability to insert tags was not particularly difficult, then the issue of saving and loading required analysis and study, since the standard functionality of Gtk libraries allows you to save text, but there is no formatting in it. After trying out various data serialization options, I came to the decision not to mix content and formatting, and these two parts of the same whole should be stored separately from each other. This is about how documents are stored <code>.docx</code> and <code>.odt</code>.</p>
<h3 id="nesting">Nesting</h3>
<p>You can also nest pages inside each other and move them between workspaces. This can be done either by simply dragging the mouse or using the context menu.</p>
<p>The nesting is not limited, but in the future this parameter may be revised after analyzing the work on large volumes and deep nesting of pages.</p>
<h2 id="to-be-continued">To be continued</h2>
<p>A start has been made, but there is still a lot of work ahead, both in adding new functionality and debugging interfaces and performance. It&rsquo;s too early to talk about release plans, those who suffer can download the source codes and build the application themselves, everything is available on GitHub.</p>
<p><strong>See you!</strong></p>

        ]]></turbo:content></item><item turbo="true"><turbo:extendedHtml>true</turbo:extendedHtml><title>Server-side Dart</title><link>https://tenderowl.com/posts/server-side-dart/</link><pubDate>Tue, 04 Oct 2022 01:26:21 +0300</pubDate><guid>https://tenderowl.com/posts/server-side-dart/</guid><description>&lt;h2 id="disclaimer">Disclaimer&lt;/h2>
&lt;p>Laska is under active development and is not supposed to be used in production. API may change dramatically in future versions.&lt;/p>
&lt;h2 id="intro">Intro&lt;/h2>
&lt;p>It&amp;rsquo;s not a secret that Dart&amp;rsquo;s popularity comes from the Flutter framework and its acceptance by the community. I love Flutter as well, but what I miss is server-side. How cool it could be to be a full-stack developer by using only Dart language?&lt;/p></description><turbo:content><![CDATA[
            <h2 id="disclaimer">Disclaimer</h2>
<p>Laska is under active development and is not supposed to be used in production. API may change dramatically in future versions.</p>
<h2 id="intro">Intro</h2>
<p>It&rsquo;s not a secret that Dart&rsquo;s popularity comes from the Flutter framework and its acceptance by the community. I love Flutter as well, but what I miss is server-side. How cool it could be to be a full-stack developer by using only Dart language?</p>
<p>There was a time, I want to start a new project and have to choose what technologies to use for the frontend and backend parts as well. Research gets me to <a href="https://aqueduct.io/">Aqueduct</a> and <a href="https://pub.dev/packages/shelf">Shelf</a>, both of them weren&rsquo;t looking actively developing and supported and that leads me to the idea to make my own small micro-framework like <a href="https://echo.labstack.com/">Echo</a> for Golang or <a href="https://bottlepy.org/">Bottle</a> for Python. And it was easy to decide: I&rsquo;ve had time and motivation :)</p>
<p>And before we get started, I&rsquo;d like to answer some questions that will definitely come up: why do we need one more framework, and what&rsquo;s the problem your framework resolves? The first one is easy: ask JavaScript developers! But OK, the real answer is competition: the more frameworks we have, the more we learn from each other and, eventually, we build better apps. The second answer: it was designed and built for micro-services and small applications. Sure, there is a way to extend it with middleware and extensions that come later, but as I said I treat it as a Dart alternative to Bottle or Echo. And when I say built I mean will be built 😄</p>
<h2 id="architecture">Architecture</h2>
<p>Laska - that&rsquo;s how the project is named - is a micro-framework, it&rsquo;s not aimed to be a full-featured app like Django. Therefore the architecture is quite simple: every request is processed by the same process while performance is based on leveraging the async nature of Dart.</p>
<p>Let&rsquo;s briefly review how it works.</p>
<p><img src="https://dev-to-uploads.s3.amazonaws.com/uploads/articles/21sorv8stnckxhl0yb21.png" alt="Async server diagram"></p>
<p>As we can see on the diagram, asynchronous server processing works like a charm :)</p>
<p>I did experiment with Isolates and multiprocessing, but finally, I decided it would require the developers to work with additional limitations. It is not the way I want to use any kind of software, so why does anyone should do it? Programming should be easy and fun therefore there is no kind of parallelism in Laska.</p>
<h2 id="lets-build-something">Let&rsquo;s build something</h2>
<p>There is a trivial way to present programming languages and frameworks called &lsquo;Hello world&rsquo;. Let&rsquo;s be trivial!</p>
<div class="rounded"><pre tabindex="0" class="chroma"><code class="language-dart" data-lang="dart"><span class="line"><span class="cl"><span class="k">import</span> <span class="s1">&#39;package:laska/laska.dart&#39;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kt">void</span> <span class="n">main</span><span class="p">()</span> <span class="kd">async</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="kd">final</span> <span class="n">laska</span> <span class="o">=</span> <span class="n">Laska</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="n">laska</span><span class="p">.</span><span class="n">GET</span><span class="p">(</span><span class="s1">&#39;/hello/:name&#39;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="p">(</span><span class="n">context</span><span class="p">)</span> <span class="kd">async</span> <span class="o">=&gt;</span> <span class="kd">await</span> <span class="n">context</span><span class="p">.</span><span class="n">Text</span><span class="p">(</span><span class="s2">&#34;Hello </span><span class="si">${</span><span class="n">context</span><span class="p">.</span><span class="n">param</span><span class="p">(</span><span class="s1">&#39;name&#39;</span><span class="p">)</span><span class="si">}</span><span class="s2">!&#34;</span><span class="p">));</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="kd">await</span> <span class="n">run</span><span class="p">(</span><span class="n">laska</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>It&rsquo;s easy to understand what&rsquo;s going on here:</p>
<ul>
<li>initialize a Laska object</li>
<li>add a handler for GET requests at the route &lsquo;/hello/:name&rsquo;, where <code>name</code> is a placeholder for context parameters</li>
<li>code a handler that returns a string with a parameter given in the path</li>
<li>and run the server</li>
</ul>
<p>That&rsquo;s it, an asynchronous server is up and running on http://localhost:3789.</p>
<h2 id="more-realistic-example">More realistic example</h2>
<p>Surely, &lsquo;Hello world&rsquo; is not a kind of real-world application someone will deploy to production. The more realistic would be to build a server that logs user requests to the stdout and has routes that require authorization.</p>
<div class="rounded"><pre tabindex="0" class="chroma"><code class="language-dart" data-lang="dart"><span class="line"><span class="cl"><span class="k">import</span> <span class="s1">&#39;package:laska/laska.dart&#39;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1">// Custom middleware that checks user access.
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="kd">class</span> <span class="nc">Acl</span> <span class="kd">implements</span> <span class="n">Middleware</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="kd">final</span> <span class="n">allowedRoles</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="n">Acl</span><span class="p">(</span><span class="k">this</span><span class="p">.</span><span class="n">allowedRoles</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="err">@</span><span class="n">override</span>
</span></span><span class="line"><span class="cl">  <span class="n">Future</span><span class="o">&lt;</span><span class="n">Function</span><span class="o">&gt;</span> <span class="n">execute</span><span class="p">(</span><span class="n">Function</span> <span class="n">next</span><span class="p">,</span> <span class="n">Context</span> <span class="n">context</span><span class="p">)</span> <span class="kd">async</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="p">(</span><span class="n">Context</span> <span class="n">c</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="c1">// In this case it&#39;s simple check:
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>      <span class="c1">// Does the request contains `role` header with `admin` value.
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>      <span class="kd">final</span> <span class="n">role</span> <span class="o">=</span> <span class="n">context</span><span class="p">.</span><span class="n">request</span><span class="p">.</span><span class="n">headers</span><span class="p">.</span><span class="n">value</span><span class="p">(</span><span class="s1">&#39;role&#39;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">      <span class="c1">// If the header&#39;s `role` is not in `allowedRoles`, reject the request.
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>      <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">allowedRoles</span><span class="p">.</span><span class="n">contains</span><span class="p">(</span><span class="n">role</span><span class="p">))</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="n">context</span><span class="p">.</span><span class="n">Text</span><span class="p">(</span><span class="s1">&#39;Role </span><span class="si">$</span><span class="n">role</span><span class="s1"> is not allowed.&#39;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">        <span class="k">return</span> <span class="kc">null</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">      <span class="p">}</span>
</span></span><span class="line"><span class="cl">      <span class="n">print</span><span class="p">(</span><span class="s1">&#39;Role </span><span class="si">$</span><span class="n">role</span><span class="s1"> is allowed.&#39;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">      <span class="c1">// Don&#39;t forget to call the handler.
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>      <span class="k">return</span> <span class="n">next</span><span class="p">(</span><span class="n">c</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="p">};</span>
</span></span><span class="line"><span class="cl">  <span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1">// Custom middleware that prints request path and given prefix.
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="kd">class</span> <span class="nc">Logger</span> <span class="kd">implements</span> <span class="n">Middleware</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="kt">String</span> <span class="n">prefix</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="n">Logger</span><span class="p">(</span><span class="k">this</span><span class="p">.</span><span class="n">prefix</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="err">@</span><span class="n">override</span>
</span></span><span class="line"><span class="cl">  <span class="n">Future</span><span class="o">&lt;</span><span class="n">Function</span><span class="o">&gt;</span> <span class="n">execute</span><span class="p">(</span><span class="n">Function</span> <span class="n">next</span><span class="p">,</span> <span class="n">Context</span> <span class="n">context</span><span class="p">)</span> <span class="kd">async</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="p">(</span><span class="n">Context</span> <span class="n">c</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="n">print</span><span class="p">(</span><span class="s1">&#39;</span><span class="si">$</span><span class="n">prefix</span><span class="s1">: Path: </span><span class="si">${</span><span class="n">context</span><span class="p">.</span><span class="n">path</span><span class="si">}</span><span class="s1">&#39;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">      <span class="c1">// Don&#39;t forget to call the handler.
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>      <span class="k">return</span> <span class="n">next</span><span class="p">(</span><span class="n">c</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="p">};</span>
</span></span><span class="line"><span class="cl">  <span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kt">void</span> <span class="n">main</span><span class="p">()</span> <span class="kd">async</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="kd">final</span> <span class="n">laska</span> <span class="o">=</span> <span class="n">Laska</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="kd">final</span> <span class="n">acl_middleware</span> <span class="o">=</span> <span class="n">Acl</span><span class="p">([</span><span class="s1">&#39;admin&#39;</span><span class="p">]);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="c1">// Add global middleware
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>  <span class="n">laska</span><span class="p">.</span><span class="n">Use</span><span class="p">(</span><span class="n">Logger</span><span class="p">(</span><span class="s1">&#39;global&#39;</span><span class="p">));</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="c1">// Create handler with per-route middlewares: logger and acl
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>  <span class="n">laska</span><span class="p">.</span><span class="n">GET</span><span class="p">(</span><span class="s1">&#39;/secret&#39;</span><span class="p">,</span> <span class="n">secretHandler</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="nl">middlewares:</span> <span class="p">{</span><span class="n">Logger</span><span class="p">(</span><span class="s1">&#39;route&#39;</span><span class="p">),</span> <span class="n">acl_middleware</span><span class="p">});</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="c1">// Add route handler, only global middleware will apply
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>  <span class="n">laska</span><span class="p">.</span><span class="n">GET</span><span class="p">(</span><span class="s1">&#39;/users&#39;</span><span class="p">,</span> <span class="n">getUsers</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="c1">// Add route with acl middleware, but only for the POST method.
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>  <span class="n">laska</span><span class="p">.</span><span class="n">POST</span><span class="p">(</span><span class="s1">&#39;/users&#39;</span><span class="p">,</span> <span class="n">getUsers</span><span class="p">,</span> <span class="nl">middlewares:</span> <span class="p">{</span><span class="n">acl_middleware</span><span class="p">});</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="kd">await</span> <span class="n">run</span><span class="p">(</span><span class="n">laska</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kt">void</span> <span class="n">secretHandler</span><span class="p">(</span><span class="n">Context</span> <span class="n">context</span><span class="p">)</span> <span class="kd">async</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="kd">await</span> <span class="n">context</span><span class="p">.</span><span class="n">Text</span><span class="p">(</span><span class="s1">&#39;You have access to secret path!&#39;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kt">void</span> <span class="n">getUsers</span><span class="p">(</span><span class="n">Context</span> <span class="n">context</span><span class="p">)</span> <span class="kd">async</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="kd">await</span> <span class="n">context</span><span class="p">.</span><span class="n">JSON</span><span class="p">([</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span><span class="s1">&#39;id&#39;</span><span class="o">:</span> <span class="m">1</span><span class="p">,</span> <span class="s1">&#39;name&#39;</span><span class="o">:</span> <span class="s1">&#39;Make something useful&#39;</span><span class="p">,</span> <span class="s1">&#39;status&#39;</span><span class="o">:</span> <span class="m">0</span><span class="p">},</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span><span class="s1">&#39;id&#39;</span><span class="o">:</span> <span class="m">2</span><span class="p">,</span> <span class="s1">&#39;name&#39;</span><span class="o">:</span> <span class="s1">&#39;Make new website&#39;</span><span class="p">,</span> <span class="s1">&#39;status&#39;</span><span class="o">:</span> <span class="m">1</span><span class="p">},</span>
</span></span><span class="line"><span class="cl">  <span class="p">]);</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>I hope this code is self-explanatory but you can ask me  any questions  in comments :)</p>
<h2 id="roadmap">Roadmap</h2>
<p>🅿️ - P is for planned.</p>
<ul>
<li>✅ Dynamic routing with placeholders</li>
<li>✅ Middlewares</li>
<li>✅ JSON handling</li>
<li>✅ Form handling</li>
<li>✅ Null-Safety</li>
<li>🅿️ Logging</li>
<li>🅿️ Serve static files</li>
<li>🅿️ Template rendering?</li>
<li>🅿️ Extensions</li>
<li>🅿️ Health checks</li>
<li>❓ What&rsquo;s more?</li>
</ul>
<h2 id="the-end">The end</h2>
<p>I&rsquo;d love to see more server-side Dart projects and hope you do too. If you think so it would be very encouraging if you star the project on <a href="https://github.com/TenderOwl/laska">GitHub</a> and like it on Dev.to.</p>
        ]]></turbo:content></item><item turbo="true"><turbo:extendedHtml>true</turbo:extendedHtml><title>What's new in Norka 1.0</title><link>https://tenderowl.com/posts/whats-new-in-norka-1-0/</link><pubDate>Mon, 21 Feb 2022 17:26:21 +0300</pubDate><guid>https://tenderowl.com/posts/whats-new-in-norka-1-0/</guid><description>&lt;p>&lt;img src="https://raw.githubusercontent.com/TenderOwl/Norka/master/data/screenshots/norka-editor.png" alt="Norka editor">&lt;/p>
&lt;p>Nearly two years ago, Norka was born, a note-taking application made specifically for elementary OS and also distributed via Flathub. This version was far from version 1.0, its functionality was limited only to essential things: editing, searching, autosaving. Later, it introduced document search, export to various formats, spell checking, convenient keyboard shortcuts for editing, the ability to preview documents in HTML and statistics, etc. This was enough to start, but there was room for improvement. One possibility might be the ability to group notes in a folder and structure the user&amp;rsquo;s resources in a certain way. So what&amp;rsquo;s new comes in version 1.0?&lt;/p></description><turbo:content><![CDATA[
            <p><img src="https://raw.githubusercontent.com/TenderOwl/Norka/master/data/screenshots/norka-editor.png" alt="Norka editor"></p>
<p>Nearly two years ago, Norka was born, a note-taking application made specifically for elementary OS and also distributed via Flathub. This version was far from version 1.0, its functionality was limited only to essential things: editing, searching, autosaving. Later, it introduced document search, export to various formats, spell checking, convenient keyboard shortcuts for editing, the ability to preview documents in HTML and statistics, etc. This was enough to start, but there was room for improvement. One possibility might be the ability to group notes in a folder and structure the user&rsquo;s resources in a certain way. So what&rsquo;s new comes in version 1.0?</p>
<h2 id="folders">Folders</h2>
<p><img src="https://raw.githubusercontent.com/TenderOwl/Norka/master/data/screenshots/norka-grid.png" alt="Folders and documents"></p>
<p>The main and most important innovation is the ability to structure documents using folders. Folders can be created by clicking on a button in the header bar or by dragging documents onto each other. In this case, you will see a window displaying a suggestion for a folder name.</p>
<p>Folders can be moved from one to another. The level is not limited, but there is a limit on a path length, which, by the way, are found under the title of the window in the title. Also, some characters cannot be used in the names, we plan to fix this in the next releases.</p>
<p>The backup function also includes the structure of the library and exports documents by putting them in the right folders. This way you will get a complete copy of the library on disk.</p>
<h2 id="new-fonts-and-updated-theme">New fonts and updated theme</h2>
<p>We continue to work on using Norka not only in terms of functionality but also in terms of UI. Starting with version 1.0 Norka will use the <a href="https://github.com/iaolo/iA-Fonts">iA Writer Duospace</a> font. Thank you for sharing them with the community. The color scheme of the editor has also been updated.</p>
<h2 id="actions">Actions</h2>
<p>In many desktop environments, it is possible to set up global hotkeys to trigger specific actions in applications. For example, take a screenshot or share a file. Now you can use the command to quickly take notes with Norka. This action can also be implemented as a command to call from hot corners in GNOME or <a href="https://elementary.io/docs/learning-the-basics#multitasking">elementary OS</a>.</p>
<h2 id="specifying-the-language-for-spell-checking">Specifying the language for spell checking</h2>
<p>In one of the latest releases, we switched from GtkSpell to use GSpell, but at the same time, Norka lost the ability to specify which language to use for this check. And while this isn&rsquo;t a problem if you&rsquo;re writing in English, it creates problems for writers in other languages. This setting can now be changed in the application.</p>
<h2 id="import-and-formatting-fixes">Import and formatting fixes</h2>
<p>As with spell checking, one might not have noticed that for non-Latin languages, hotkeys are used to format text, leading to unexpected results. For example, when inserting a title, some of the text could be erased. Now we have fixed this problem, Norka correctly inserts formatting without text loss.</p>
<p>We also fixed auto-completion of lists: when pressing Enter, Norka will check if an item text has been entered, and if not, then inserting the list formatting ends.</p>
<p>For those who use drag-n-drop to import, the problem turned into the fact that when importing several files in sequence, the documents / those that were used the first time that time were re-imported instead of the last ones. We have fixed this issue, there is no more reason to worry about erroneous imports and prevent the deletion of redundant copies.</p>
<h2 id="conclusion">Conclusion</h2>
<p>We keep improving Norka even further. We aimed to become the best writing app and note editor for Linux in the future, and possibly not only a Linux.</p>
<p>Thanks to everyone who uses Norka!</p>

        ]]></turbo:content></item><item turbo="true"><turbo:extendedHtml>true</turbo:extendedHtml><title>Elementary Os Contractor in Action</title><link>https://tenderowl.com/posts/elementary-os-contractor-in-action/</link><pubDate>Sun, 24 Oct 2021 00:04:31 +0300</pubDate><guid>https://tenderowl.com/posts/elementary-os-contractor-in-action/</guid><description>&lt;p>How to use elementary OS Contractor to simplify your daily routines.&lt;/p></description><turbo:content><![CDATA[
            <p>How to use elementary OS Contractor to simplify your daily routines.</p>
<h2 id="intro">Intro</h2>
<p>Almost two years ago I lost my iMac and it brought me an idea to see what&rsquo;s changed in the Linux world for 10 years. Starting from Ubuntu and Fedora I finally came to the elementary OS project&rsquo;s website. And it was quite astonishing. In comparison to the other distributions elementary had its view on how the operating system should view, how to make apps, its own design language and <a href="https://docs.elementary.io/develop/">developers documentation</a>. Thereby the decision to move to this OS was easy.</p>
<p>For now, it is almost two years since I moved from macOS to elementary OS as my daily driver. And, as I said earlier, I&rsquo;m a developer. After looking for a native text editor I made myself think to make <a href="https://tenderowl.com/work/norka/">Norka</a>. But, this article is about the distinct application. Actually, I&rsquo;m not the author of it, the app is already a part of elementary itself - <a href="https://github.com/elementary/contractor">Contractor</a>.</p>
<h2 id="contractor">Contractor</h2>
<p>The idea is very simple: users can write a contract - a file which instructs the Contractor how to call the command - and it comes available in any supported apps: Files, Photos, etc.</p>
<h2 id="action">Action</h2>
<p>I suppose you&rsquo;re already got the idea of how the Contractor can help you in your daily work, but let&rsquo;s make a contract that&hellip; well.. help you share any file via Telegram. What do we need for it?</p>
<ul>
<li>install Telegram</li>
<li>get the command</li>
<li>create a contract</li>
<li>test it!</li>
</ul>
<p>Because it differences in one case of installation on another I will write for the <a href="https://flathub.org/apps/details/org.telegram.desktop">Flathub version</a>.</p>
<p>Let&rsquo;s dive in!</p>
<h3 id="install-telegram">Install Telegram</h3>
<p>As of <a href="https://elementary.io">elementary OS</a> user, I can simply go to flathub.org and press the &ldquo;Install&rdquo; button. Sideload app do the job. But what about other distros? Well, the right way is completely covered on the Flathub site so we don&rsquo;t wanna go through all steps :)</p>
<h3 id="get-the-right-command">Get the right command</h3>
<p>To write a good contract we need to find out what command does the job we need. In some cases, it could be easily extracted from <code>--help</code> info or <code>man</code> pages. Luckily Telegram command-line switches are fully described in the GitHub Wiki: <a href="https://github.com/telegramdesktop/tdesktop/wiki/Command-Line-Switches">https://github.com/telegramdesktop/tdesktop/wiki/Command-Line-Switches</a>. <code>-sendpath &lt;file&gt;</code> - the desired switch.</p>
<p>One part is done, let&rsquo;s figure out the second one. To run the application with a command-line switch installed via Flatpak we have to call <code>flatpak run</code> command. Thus our command will look like this:</p>
<div class="rounded"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">flatpak run org.telegram.desktop -sendpath %f
</span></span></code></pre></div><p>This is already enough to make a contract!</p>
<h3 id="contract">Contract</h3>
<p>Every contract should be placed inside <code>~/.local/share/contractor</code> if we talking about a specific user or inside <code>/usr/share/contractor</code> for the system-wide availability.</p>
<p>Simple <code>.contract</code> file example:</p>
<div class="rounded"><pre tabindex="0" class="chroma"><code class="language-ini" data-lang="ini"><span class="line"><span class="cl"><span class="k">[Contractor Entry]</span>
</span></span><span class="line"><span class="cl"><span class="na">Name</span><span class="o">=</span><span class="s">Mount</span>
</span></span><span class="line"><span class="cl"><span class="na">MimeType</span><span class="o">=</span><span class="s">application/x-cd-image;application/x-raw-disk-image</span>
</span></span><span class="line"><span class="cl"><span class="na">Exec</span><span class="o">=</span><span class="s">gnome-disk-image-mounter %f</span>
</span></span></code></pre></div><p>This example shows how to make a command that will mount disk images to the filesystem. We need something different.</p>
<p>Create <code>telegram-share.contract</code> file and open it in <a href="https://github.com/elementary/code">Code</a> or your text editor of choice.
Change <code>Name</code> to <code>Share via Telegram</code> and <code>Exec</code> field to a command we discovered earlier. If English is not your primary language you can add localized <code>Name</code> by adding <code>Name[language code]</code> option. For example:</p>
<div class="rounded"><pre tabindex="0" class="chroma"><code class="language-ini" data-lang="ini"><span class="line"><span class="cl"><span class="na">Name[ru]</span><span class="o">=</span><span class="s">Поделиться в Telegram</span>
</span></span></code></pre></div><p>Last but not least we have to set what mime-types has to activate the Share command. Of course, it doesn&rsquo;t mean to be only disk images
and here is the special type - <code>!inode</code> - which means any file you select no matter archive it or pdf.</p>
<p>Let&rsquo;s see what we&rsquo;ve got!</p>
<div class="rounded"><pre tabindex="0" class="chroma"><code class="language-ini" data-lang="ini"><span class="line"><span class="cl"><span class="k">[Contractor Entry]</span>
</span></span><span class="line"><span class="cl"><span class="na">Name</span><span class="o">=</span><span class="s">Share via Telegram</span>
</span></span><span class="line"><span class="cl"><span class="na">Name[ru]</span><span class="o">=</span><span class="s">Поделиться в Telegram</span>
</span></span><span class="line"><span class="cl"><span class="na">Icon</span><span class="o">=</span><span class="s">telegram</span>
</span></span><span class="line"><span class="cl"><span class="na">MimeType</span><span class="o">=</span><span class="s">!inode;</span>
</span></span><span class="line"><span class="cl"><span class="na">Exec</span><span class="o">=</span><span class="s">flatpak run org.telegram.desktop -sendpath %f</span>
</span></span></code></pre></div><p>Now save your contract and test it by clicking on any file in Files and pressing the &ldquo;Share via Telegram&rdquo; option.</p>
<p>More examples listed in the <a href="https://github.com/elementary/contractor#examples">Contractor repository</a>.</p>
<h2 id="bonus">Bonus</h2>
<h3 id="notification">Notification</h3>
<p>Sometimes you wanna indicate to yourself when the contract will be completed. To make it happen it&rsquo;s not enough to call just one command, you need to send a notification somehow. Again, elementary already has an application for it named <code>notify-send</code>, and all we need is to combine all the commands into one with help of <code>sh</code>. As you may see in the Tips section it is easy enough:</p>
<div class="rounded"><pre tabindex="0" class="chroma"><code class="language-ini" data-lang="ini"><span class="line"><span class="cl"><span class="na">Exec</span><span class="o">=</span><span class="s">sh -c &#34;flatpak run org.telegram.desktop -sendpath %f &amp;&amp; notify-send &#39;File sent&#39; &#39;%f successfully sent via Telegram&#39;&#34;</span>
</span></span></code></pre></div><p>Final version:</p>
<div class="rounded"><pre tabindex="0" class="chroma"><code class="language-ini" data-lang="ini"><span class="line"><span class="cl"><span class="k">[Contractor Entry]</span>
</span></span><span class="line"><span class="cl"><span class="na">Name</span><span class="o">=</span><span class="s">Share via Telegram</span>
</span></span><span class="line"><span class="cl"><span class="na">Name[ru]</span><span class="o">=</span><span class="s">Поделиться в Telegram</span>
</span></span><span class="line"><span class="cl"><span class="na">Icon</span><span class="o">=</span><span class="s">telegram</span>
</span></span><span class="line"><span class="cl"><span class="na">MimeType</span><span class="o">=</span><span class="s">!inode;</span>
</span></span><span class="line"><span class="cl"><span class="na">Exec</span><span class="o">=</span><span class="s">sh -c &#34;flatpak run org.telegram.desktop -sendpath %f &amp;&amp; notify-send &#39;File sent&#39; &#39;%f successfully sent via Telegram&#39;&#34;</span>
</span></span></code></pre></div><h3 id="install-deb-packages">Install .deb packages</h3>
<div class="rounded"><pre tabindex="0" class="chroma"><code class="language-ini" data-lang="ini"><span class="line"><span class="cl"><span class="k">[Contractor Entry]</span>
</span></span><span class="line"><span class="cl"><span class="na">Name</span><span class="o">=</span><span class="s">Install app</span>
</span></span><span class="line"><span class="cl"><span class="na">Name[ru]</span><span class="o">=</span><span class="s">Установить приложение</span>
</span></span><span class="line"><span class="cl"><span class="na">MimeType</span><span class="o">=</span><span class="s">application/vnd.debian.binary-package</span>
</span></span><span class="line"><span class="cl"><span class="na">Description</span><span class="o">=</span><span class="s">Quickly install .deb package</span>
</span></span><span class="line"><span class="cl"><span class="na">Exec</span><span class="o">=</span><span class="s">sh -c &#34;pkexec apt install %u &amp;&amp; notify-send &#39;the App installed&#39; -a Apt&#34;</span>
</span></span></code></pre></div><h2 id="tips">Tips:</h2>
<ul>
<li>Use pkexec to ask for root permissions. Example:
<div class="rounded"><pre tabindex="0" class="chroma"><code class="language-ini" data-lang="ini"><span class="line"><span class="cl"><span class="na">Exec</span><span class="o">=</span><span class="s">pkexec chmod +x %U</span>
</span></span></code></pre></div></li>
<li>To call multiple commands at once try to use sh:
<div class="rounded"><pre tabindex="0" class="chroma"><code class="language-ini" data-lang="ini"><span class="line"><span class="cl"><span class="na">Exec</span><span class="o">=</span><span class="s">sh -c &#34;echo &#39;step 1&#39; &amp;&amp; notify-send &#39;Done&#39; &amp;&amp; echo &#39;step 2&#39;&#34;</span>
</span></span></code></pre></div></li>
</ul>
        ]]></turbo:content></item><item turbo="true"><turbo:extendedHtml>true</turbo:extendedHtml><title>Turtle</title><link>https://tenderowl.com/work/turtle/</link><pubDate>Tue, 10 Aug 2021 10:38:12 +0300</pubDate><guid>https://tenderowl.com/work/turtle/</guid><description>&lt;p>Add any launcherless applications to the Applications Menu—even AppImages or downloaded binaries. Just open Turtle, navigate to the app, and select a name and image to show in the Applications Menu.&lt;/p>
&lt;div class="row">
&lt;div class="col">
&lt;div class="text-center">
&lt;video controls>
&lt;source src="https://tenderowl.com/work/images/makeadesktop.mp4" type="video/mp4">
Your browser doesn't support HTML5 video tag.
&lt;/video>
&lt;/div>
&lt;/div>
&lt;/div>
&lt;div class="row">
&lt;div class="col">
&lt;h2 class="text-6xl amatic mb-10">Get Turtle&lt;/h2>
&lt;div class=" mb-4">
&lt;a href="https://appcenter.elementary.io/com.github.tenderowl.turtle"
onclick="ym(48108788,'reachGoal','appcenter')">&lt;img
src="https://appcenter.elementary.io/badge.svg" alt="Get it on AppCenter"
style="height: 50px;">&lt;/a>
&lt;/div>
&lt;p class="py-8">
If you feel enough power to contribute or fork feel free to do it. Source code
&lt;a href="https://github.com/TenderOwl/Turtle/" title="" class="px-1">
&lt;svg width="20" height="19" fill="currentColor" xmlns="http://www.w3.org/2000/svg" class="inline">
&lt;path d="M10 0c1.814 0 3.487.435 5.02 1.306a9.827 9.827 0 0 1 3.639 3.542A9.33 9.33 0 0 1 20 9.734c0 2.121-.636 4.03-1.908 5.723a9.783 9.783 0 0 1-4.928 3.518c-.234.042-.408.012-.52-.09a.49.49 0 0 1-.17-.38l.006-.969c.005-.621.007-1.19.007-1.705 0-.82-.226-1.42-.677-1.8.495-.05.94-.126 1.335-.228a5.4 5.4 0 0 0 1.223-.494 3.62 3.62 0 0 0 1.055-.843c.282-.334.512-.777.69-1.33.178-.554.267-1.19.267-1.909a3.7 3.7 0 0 0-1.028-2.61c.32-.77.286-1.631-.105-2.586-.243-.076-.594-.03-1.054.14-.46.168-.86.354-1.198.557l-.495.304a9.478 9.478 0 0 0-2.5-.33c-.86 0-1.693.11-2.5.33a11.6 11.6 0 0 0-.553-.342c-.23-.135-.593-.298-1.088-.488-.494-.19-.863-.247-1.106-.171-.391.955-.426 1.816-.105 2.585A3.7 3.7 0 0 0 3.62 9.227c0 .719.089 1.352.267 1.902.178.549.406.993.683 1.33.278.339.627.622 1.048.85a5.4 5.4 0 0 0 1.224.494c.395.102.84.178 1.335.228-.338.305-.551.74-.638 1.306a2.631 2.631 0 0 1-.586.19 3.782 3.782 0 0 1-.742.063c-.287 0-.57-.09-.853-.272a2.256 2.256 0 0 1-.723-.792 2.068 2.068 0 0 0-.631-.66c-.256-.168-.471-.27-.645-.304l-.26-.038c-.182 0-.308.02-.378.057-.07.038-.09.087-.065.146.026.06.065.118.117.178.053.059.109.11.17.152l.09.063c.192.085.38.245.567.482.187.236.324.452.41.646l.13.292c.113.32.304.58.574.78.269.198.56.325.872.38.312.054.614.084.905.088.29.004.532-.01.723-.044l.299-.05c0 .32.002.694.007 1.12l.006.692a.49.49 0 0 1-.17.38c-.112.101-.286.13-.52.089a9.783 9.783 0 0 1-4.928-3.518C.636 13.763 0 11.855 0 9.734a9.33 9.33 0 0 1 1.341-4.886 9.827 9.827 0 0 1 3.64-3.542C6.512.436 8.185 0 10 0zM3.79 13.98c.025-.058-.005-.11-.092-.151-.087-.026-.143-.017-.17.025-.025.06.005.11.092.152.078.05.134.042.17-.025zm.403.432c.06-.043.052-.11-.026-.203-.087-.076-.157-.089-.209-.038-.06.042-.052.11.026.203.087.084.157.097.209.038zm.39.57c.078-.06.078-.14 0-.24-.07-.11-.143-.136-.221-.077-.078.042-.078.118 0 .228.078.11.152.14.221.089zm.547.532c.07-.067.052-.148-.052-.24-.104-.102-.19-.115-.26-.039-.078.068-.061.148.052.241.104.102.19.114.26.038zm.742.317c.026-.093-.03-.16-.169-.203-.13-.033-.213-.004-.247.09-.035.092.021.155.169.19.13.05.213.025.247-.077zm.82.064c0-.11-.073-.157-.22-.14-.14 0-.209.047-.209.14 0 .11.074.156.221.139.14 0 .209-.046.209-.14zm.756-.127c-.017-.093-.096-.131-.234-.114-.14.025-.2.088-.183.19.018.101.096.135.235.101.139-.034.2-.093.182-.177z" fill-rule="nonzero">&lt;/path>
&lt;/svg>
Available on GitHub
&lt;/a>
&lt;/p></description><turbo:content><![CDATA[
            <p>Add any launcherless applications to the Applications Menu—even AppImages or downloaded binaries. Just open Turtle, navigate to the app, and select a name and image to show in the Applications Menu.</p>
<div class="row">
    <div class="col">
        <div class="text-center">
            <video controls>
                <source src="/work/images/makeadesktop.mp4"  type="video/mp4">
                Your browser doesn't support HTML5 video tag.
            </video>
        </div>
    </div>
</div>
<div class="row">
    <div class="col">
        <h2 class="text-6xl amatic mb-10">Get Turtle</h2>
        <div class=" mb-4">
            <a href="https://appcenter.elementary.io/com.github.tenderowl.turtle"
                    onclick="ym(48108788,'reachGoal','appcenter')"><img
                        src="https://appcenter.elementary.io/badge.svg" alt="Get it on AppCenter"
                        style="height: 50px;"></a>
        </div>
        <p class="py-8">
            If you feel enough power to contribute or fork feel free to do it. Source code
            <a href="https://github.com/TenderOwl/Turtle/" title="" class="px-1">
            <svg width="20" height="19" fill="currentColor" xmlns="http://www.w3.org/2000/svg" class="inline">
                    <path d="M10 0c1.814 0 3.487.435 5.02 1.306a9.827 9.827 0 0 1 3.639 3.542A9.33 9.33 0 0 1 20 9.734c0 2.121-.636 4.03-1.908 5.723a9.783 9.783 0 0 1-4.928 3.518c-.234.042-.408.012-.52-.09a.49.49 0 0 1-.17-.38l.006-.969c.005-.621.007-1.19.007-1.705 0-.82-.226-1.42-.677-1.8.495-.05.94-.126 1.335-.228a5.4 5.4 0 0 0 1.223-.494 3.62 3.62 0 0 0 1.055-.843c.282-.334.512-.777.69-1.33.178-.554.267-1.19.267-1.909a3.7 3.7 0 0 0-1.028-2.61c.32-.77.286-1.631-.105-2.586-.243-.076-.594-.03-1.054.14-.46.168-.86.354-1.198.557l-.495.304a9.478 9.478 0 0 0-2.5-.33c-.86 0-1.693.11-2.5.33a11.6 11.6 0 0 0-.553-.342c-.23-.135-.593-.298-1.088-.488-.494-.19-.863-.247-1.106-.171-.391.955-.426 1.816-.105 2.585A3.7 3.7 0 0 0 3.62 9.227c0 .719.089 1.352.267 1.902.178.549.406.993.683 1.33.278.339.627.622 1.048.85a5.4 5.4 0 0 0 1.224.494c.395.102.84.178 1.335.228-.338.305-.551.74-.638 1.306a2.631 2.631 0 0 1-.586.19 3.782 3.782 0 0 1-.742.063c-.287 0-.57-.09-.853-.272a2.256 2.256 0 0 1-.723-.792 2.068 2.068 0 0 0-.631-.66c-.256-.168-.471-.27-.645-.304l-.26-.038c-.182 0-.308.02-.378.057-.07.038-.09.087-.065.146.026.06.065.118.117.178.053.059.109.11.17.152l.09.063c.192.085.38.245.567.482.187.236.324.452.41.646l.13.292c.113.32.304.58.574.78.269.198.56.325.872.38.312.054.614.084.905.088.29.004.532-.01.723-.044l.299-.05c0 .32.002.694.007 1.12l.006.692a.49.49 0 0 1-.17.38c-.112.101-.286.13-.52.089a9.783 9.783 0 0 1-4.928-3.518C.636 13.763 0 11.855 0 9.734a9.33 9.33 0 0 1 1.341-4.886 9.827 9.827 0 0 1 3.64-3.542C6.512.436 8.185 0 10 0zM3.79 13.98c.025-.058-.005-.11-.092-.151-.087-.026-.143-.017-.17.025-.025.06.005.11.092.152.078.05.134.042.17-.025zm.403.432c.06-.043.052-.11-.026-.203-.087-.076-.157-.089-.209-.038-.06.042-.052.11.026.203.087.084.157.097.209.038zm.39.57c.078-.06.078-.14 0-.24-.07-.11-.143-.136-.221-.077-.078.042-.078.118 0 .228.078.11.152.14.221.089zm.547.532c.07-.067.052-.148-.052-.24-.104-.102-.19-.115-.26-.039-.078.068-.061.148.052.241.104.102.19.114.26.038zm.742.317c.026-.093-.03-.16-.169-.203-.13-.033-.213-.004-.247.09-.035.092.021.155.169.19.13.05.213.025.247-.077zm.82.064c0-.11-.073-.157-.22-.14-.14 0-.209.047-.209.14 0 .11.074.156.221.139.14 0 .209-.046.209-.14zm.756-.127c-.017-.093-.096-.131-.234-.114-.14.025-.2.088-.183.19.018.101.096.135.235.101.139-.034.2-.093.182-.177z" fill-rule="nonzero"></path>
                </svg>
                Available on GitHub
            </a>
        </p>
    </div>
</div>
</div>

        ]]></turbo:content></item><item turbo="true"><turbo:extendedHtml>true</turbo:extendedHtml><title>How to use WebSockets with Python and GTK+</title><link>https://tenderowl.com/posts/how-to-use-websockets-with-python-and-gtk-copy/</link><pubDate>Fri, 18 Dec 2020 11:26:21 +0300</pubDate><guid>https://tenderowl.com/posts/how-to-use-websockets-with-python-and-gtk-copy/</guid><description>&lt;p>Hey there! I&amp;rsquo;m here to present you an amazing trip to the native apps with WebSockets!&lt;/p></description><turbo:content><![CDATA[
            <p>Hey there! I&rsquo;m here to present you an amazing trip to the native apps with WebSockets!</p>
<p>In this current case we will be using these technologies:</p>
<ul>
<li>Python</li>
<li>GTK+</li>
<li>Libsoup</li>
<li>Websocket messaging</li>
</ul>
<p>If you&rsquo;re wondering how to build a native GTK+ chat app, let&rsquo;s dive with me!</p>
<h2 id="theory">Theory</h2>
<p><a href="https://en.wikipedia.org/wiki/WebSocket">WebSockets</a> is a protocol designed to provide a consistent connection between the client and a server where both can send data to each other. It&rsquo;s like a wire over the Internet or a tube, distinct of regular HTTP, where the client only can send data to the server and it gets a response immediately.</p>
<p>Benefits of this approach are quite obvious: the server can push the data to the client when it comes to server from another client or generated somewhere on the backend. And the client doesn&rsquo;t need to refresh the page or do something to fetch new data, it just comes to the client by itself.</p>
<p>More information about WebSockets you can find on the <a href="http://websocket.org/aboutwebsocket.html">websocket.org</a>.</p>
<p>What I want to show you in this article it is how to connect to a WebSocket server and send or receive data from it in a native GTK+ application.</p>
<p>I will code my app in elementary OS using <a href="https://wiki.gnome.org/Apps/Builder">GNOME Builder</a>, but the same logic should work anywhere where <a href="https://gtk.org">GTK+</a> and <a href="https://libsoup.org">libsoup</a> are available and all code could be easily adapted even for macOS.</p>
<h2 id="ui">UI</h2>
<p>The app logic is quite simple yet representative. The UI will consist of two screens (<code>Gtk.Grid</code>) placed in <code>Gtk.Stack</code>. The first screen contains widgets required to connect to the server such as <code>Gtk.Entry</code> for server address, button to initiate a connection and a spinner to show the state.</p>
<p><img src="https://dev-to-uploads.s3.amazonaws.com/i/6peeanzovpi8tbpp38rn.png" alt="First page"></p>
<p>The second screen is more complex. Purpose of this is to enter the message, send it to the server and show the server&rsquo;s response in <code>Gtk.TextView</code>. Thus we need <code>Gtk.Grid</code> to place widgets. <code>Gtk.Entry</code> and <code>Gtk.Button</code> the same way we did it on the first page. And to display log we need <code>Gtk.TextView</code> placed within <code>Gtk.ScrolledWindow</code> in case there will be much more logs than the window can contain and the scrolling will be as much useful feature as it could :)</p>
<p><img src="https://dev-to-uploads.s3.amazonaws.com/i/b9we4xc3cwi202qr5c86.png" alt="Second page"></p>
<h3 id="connection-page">Connection page</h3>
<p>Let&rsquo;s start building! Create a new project in Builder, as the language, we will use Python 3.6+.</p>
<p><img src="https://dev-to-uploads.s3.amazonaws.com/i/6s87by1ljgku3jsp9h5p.png" alt="New project"></p>
<p>IDE will generate a base app structure with all we need, and all we need is just two files: <code>window.ui</code> and <code>window.py</code>.</p>
<p>Firstly, we need a correct UI for our app, so open <code>window.ui</code>. file and replace the &ldquo;hello world&rdquo; <code>Label</code> with <code>Gtk.Stack</code>. We will need two pages.</p>
<p><img src="https://dev-to-uploads.s3.amazonaws.com/i/h289zyc16lxbjwx4o840.png" alt="Stack properties"></p>
<p>Name the first screen &ldquo;connection&rdquo;, and place onto it <code>Gtk.Grid</code> with 3 rows and columns. In the central cell place <code>Gtk.Box</code> with 4 rows:</p>
<ol>
<li><code>Gtk.Label</code> with text &ldquo;Connection&rdquo;</li>
<li><code>Gtk.Entry</code>. Set its id to &ldquo;host_entry&rdquo;</li>
<li><code>Gtk.Button</code> with id &ldquo;connect_btn&rdquo;</li>
<li><code>Gtk.Spinner</code>. Call it whatever you like, mine is just a &ldquo;spinner&rdquo; :)</li>
</ol>
<p>That&rsquo;s enough for the &ldquo;Connection&rdquo; page.</p>
<h3 id="chat-page">Chat page</h3>
<p>Moving further. The second screen is a bit more complex. Again, we need <code>Gtk.Grid</code> to put all the widgets we need. But for now, it will be a grid with just 2 rows and 2 columns.</p>
<p>First row with two widgets in its own cells:</p>
<ul>
<li><code>Gtk.Entry</code> with id &ldquo;message_entry&rdquo;</li>
<li><code>Gtk.Button</code> with id &ldquo;send_btn&rdquo;</li>
</ul>
<p>On the second row place <code>Gtk.ScrolledWindow</code>, no id required because we don&rsquo;t need to call its method. Inside this ScrolledWindow put a new <code>Gtk.TextView</code> widget. Set its id to &ldquo;log_view&rdquo;.</p>
<p>To make it looks more convenient, set <em>width</em> = 2 in the container&rsquo;s props inside attribute editor for ScrolledWindow.</p>
<p><img src="https://dev-to-uploads.s3.amazonaws.com/i/oiu7wgjgguqg4g0dn4ue.png" alt="ScrolledWindow packing"></p>
<h2 id="logic">Logic</h2>
<p>Ok, out UI is ready enough to build some logic for it, finally :)</p>
<p>Open <code>window.py</code> and add class vars to connect with widgets from the <code>.ui</code> file.</p>
<div class="rounded"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-python3" data-lang="python3"><span class="line"><span class="cl"><span class="c1"># Stack with pages</span>
</span></span><span class="line"><span class="cl"><span class="n">screens</span> <span class="o">=</span> <span class="n">Gtk</span><span class="o">.</span><span class="n">Template</span><span class="o">.</span><span class="n">Child</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Connection page widgets</span>
</span></span><span class="line"><span class="cl"><span class="n">host_entry</span> <span class="o">=</span> <span class="n">Gtk</span><span class="o">.</span><span class="n">Template</span><span class="o">.</span><span class="n">Child</span><span class="p">()</span>
</span></span><span class="line"><span class="cl"><span class="n">connect_btn</span> <span class="o">=</span> <span class="n">Gtk</span><span class="o">.</span><span class="n">Template</span><span class="o">.</span><span class="n">Child</span><span class="p">()</span>
</span></span><span class="line"><span class="cl"><span class="n">disconnect_btn</span> <span class="o">=</span> <span class="n">Gtk</span><span class="o">.</span><span class="n">Template</span><span class="o">.</span><span class="n">Child</span><span class="p">()</span>
</span></span><span class="line"><span class="cl"><span class="n">spinner</span> <span class="o">=</span> <span class="n">Gtk</span><span class="o">.</span><span class="n">Template</span><span class="o">.</span><span class="n">Child</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Chat page widgets</span>
</span></span><span class="line"><span class="cl"><span class="n">send_btn</span> <span class="o">=</span> <span class="n">Gtk</span><span class="o">.</span><span class="n">Template</span><span class="o">.</span><span class="n">Child</span><span class="p">()</span>
</span></span><span class="line"><span class="cl"><span class="n">message_entry</span> <span class="o">=</span> <span class="n">Gtk</span><span class="o">.</span><span class="n">Template</span><span class="o">.</span><span class="n">Child</span><span class="p">()</span>
</span></span><span class="line"><span class="cl"><span class="n">log_view</span> <span class="o">=</span> <span class="n">Gtk</span><span class="o">.</span><span class="n">Template</span><span class="o">.</span><span class="n">Child</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Declare some class-vars</span>
</span></span><span class="line"><span class="cl"><span class="bp">self</span><span class="o">.</span><span class="n">session</span> <span class="o">=</span> <span class="kc">None</span>
</span></span><span class="line"><span class="cl"><span class="bp">self</span><span class="o">.</span><span class="n">connection</span> <span class="o">=</span> <span class="kc">None</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>Well, now we are able to call methods of these widgets. But also, we have to handle its signals. Let&rsquo;s connect &ldquo;connect_btn&rdquo;:</p>
<div class="rounded"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span><span class="lnt">19
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="k">def</span> <span class="fm">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="o">**</span><span class="n">kwargs</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">    <span class="c1"># Some app logic</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="bp">self</span><span class="o">.</span><span class="n">connect_btn</span><span class="o">.</span><span class="n">connect</span><span class="p">(</span><span class="s1">&#39;clicked&#39;</span><span class="p">,</span> <span class="bp">self</span><span class="o">.</span><span class="n">on_connect_clicked</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">on_connect_clicked</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">widget</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">    <span class="c1"># Check if there is a text inside the host_entry</span>
</span></span><span class="line"><span class="cl">    <span class="c1"># and grab focus if its empty</span>
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="ow">not</span> <span class="bp">self</span><span class="o">.</span><span class="n">host_entry</span><span class="o">.</span><span class="n">get_text</span><span class="p">():</span>
</span></span><span class="line"><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">host_entry</span><span class="o">.</span><span class="n">grab_focus</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">        <span class="k">return</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1"># Show user we are trying to connect</span>
</span></span><span class="line"><span class="cl">    <span class="bp">self</span><span class="o">.</span><span class="n">spinner</span><span class="o">.</span><span class="n">start</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1"># Init libsoup Session, create message and start connection</span>
</span></span><span class="line"><span class="cl">    <span class="bp">self</span><span class="o">.</span><span class="n">session</span> <span class="o">=</span> <span class="n">Soup</span><span class="o">.</span><span class="n">Session</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">    <span class="n">msg</span> <span class="o">=</span> <span class="n">Soup</span><span class="o">.</span><span class="n">Message</span><span class="o">.</span><span class="n">new</span><span class="p">(</span><span class="s2">&#34;GET&#34;</span><span class="p">,</span> <span class="bp">self</span><span class="o">.</span><span class="n">host_entry</span><span class="o">.</span><span class="n">get_text</span><span class="p">())</span>
</span></span><span class="line"><span class="cl">    <span class="bp">self</span><span class="o">.</span><span class="n">session</span><span class="o">.</span><span class="n">websocket_connect_async</span><span class="p">(</span><span class="n">msg</span><span class="p">,</span> <span class="kc">None</span><span class="p">,</span> <span class="kc">None</span><span class="p">,</span> <span class="kc">None</span><span class="p">,</span> <span class="bp">self</span><span class="o">.</span><span class="n">on_connection</span><span class="p">)</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>The code above is quite straightforward:</p>
<ul>
<li>Get the server address</li>
<li>Create new <a href="https://lazka.github.io/pgi-docs/#Soup-2.4/classes/Session.html"><code>Session</code></a></li>
<li>Init the connection with <a href="https://lazka.github.io/pgi-docs/#Soup-2.4/classes/Session.html%23Soup.Session.websocket_connect_async"><code>websocket_connect_async</code></a></li>
</ul>
<p>As a callback for connection function, we put <code>on_connection</code> method, which takes the result of this operation and finish the process.</p>
<div class="rounded"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span><span class="lnt">19
</span><span class="lnt">20
</span><span class="lnt">21
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">on_connection</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">session</span><span class="p">,</span> <span class="n">result</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">    <span class="k">try</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">        <span class="c1"># Finish connection process</span>
</span></span><span class="line"><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">connection</span> <span class="o">=</span> <span class="n">session</span><span class="o">.</span><span class="n">websocket_connect_finish</span><span class="p">(</span><span class="n">result</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        
</span></span><span class="line"><span class="cl">        <span class="c1"># If we here, the connection went correctly</span>
</span></span><span class="line"><span class="cl">        <span class="c1"># Connect `message` handler to the connection</span>
</span></span><span class="line"><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">connection</span><span class="o">.</span><span class="n">connect</span><span class="p">(</span><span class="s1">&#39;message&#39;</span><span class="p">,</span> <span class="bp">self</span><span class="o">.</span><span class="n">on_message</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        
</span></span><span class="line"><span class="cl">        <span class="c1"># Show to user all is OK and switch to the Chat page</span>
</span></span><span class="line"><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">screens</span><span class="o">.</span><span class="n">set_visible_child_name</span><span class="p">(</span><span class="s1">&#39;chat&#39;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">disconnect_btn</span><span class="o">.</span><span class="n">set_visible</span><span class="p">(</span><span class="kc">True</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    
</span></span><span class="line"><span class="cl">    <span class="k">except</span> <span class="ne">Exception</span> <span class="k">as</span> <span class="n">e</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">        <span class="c1"># In case something goes wrong just print an exception</span>
</span></span><span class="line"><span class="cl">        <span class="nb">print</span><span class="p">(</span><span class="n">e</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">session</span> <span class="o">=</span> <span class="kc">None</span>
</span></span><span class="line"><span class="cl">    
</span></span><span class="line"><span class="cl">    <span class="k">finally</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">        <span class="c1"># Don&#39;t forget to show a user that process is finished.</span>
</span></span><span class="line"><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">spinner</span><span class="o">.</span><span class="n">stop</span><span class="p">()</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>We are almost done! All we need to do more is to handle click event from the &ldquo;send_btn&rdquo; and display messages in the &ldquo;log_view&rdquo;.</p>
<div class="rounded"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">on_message</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">connection</span><span class="p">,</span> <span class="n">msg_type</span><span class="p">,</span> <span class="n">message</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">    <span class="n">msg</span> <span class="o">=</span> <span class="sa">f</span><span class="s1">&#39;&lt;b&gt;RECEIVED:&lt;/b&gt; </span><span class="si">{</span><span class="n">message</span><span class="o">.</span><span class="n">get_data</span><span class="p">()</span><span class="o">.</span><span class="n">decode</span><span class="p">()</span><span class="si">}</span><span class="se">\n</span><span class="s1">&#39;</span>
</span></span><span class="line"><span class="cl">    <span class="bp">self</span><span class="o">.</span><span class="n">buffer</span><span class="o">.</span><span class="n">insert_markup</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">buffer</span><span class="o">.</span><span class="n">get_start_iter</span><span class="p">(),</span>
</span></span><span class="line"><span class="cl">                              <span class="n">msg</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                              <span class="nb">len</span><span class="p">(</span><span class="n">msg</span><span class="p">))</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>Could it be more simple than that? :wink: Just to display a message in a more readable way we add some Pango markup and use <a href="https://lazka.github.io/pgi-docs/#Gtk-3.0/classes/TextBuffer.html%23Gtk.TextBuffer.insert_markup"><code>insert_markup</code></a> method of <code>Gtk.TextBuffer</code> to&hellip; insert markup!</p>
<p>Last, but not least required thing is a handler for &ldquo;send_btn&rdquo; signal.</p>
<div class="rounded"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">on_send_clicked</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">widget</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">    <span class="c1"># Get the message from the Entry</span>
</span></span><span class="line"><span class="cl">    <span class="n">msg</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">message_entry</span><span class="o">.</span><span class="n">get_text</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1"># If there is no message - grab focus.</span>
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="ow">not</span> <span class="n">msg</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">message_entry</span><span class="o">.</span><span class="n">grab_focus</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">        <span class="k">return</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1"># Send a text to the server.</span>
</span></span><span class="line"><span class="cl">    <span class="bp">self</span><span class="o">.</span><span class="n">connection</span><span class="o">.</span><span class="n">send_text</span><span class="p">(</span><span class="n">msg</span><span class="p">)</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>For our simple case, all we need is just the <a href="https://lazka.github.io/pgi-docs/#Soup-2.4/classes/WebsocketConnection.html%23Soup.WebsocketConnection.send_text"><code>send_text</code></a> method. But libsoup could do a lot of different stuff, such as <a href="https://lazka.github.io/pgi-docs/#Soup-2.4/classes/WebsocketConnection.html%23Soup.WebsocketConnection.send_binary"><code>send_binary</code></a> or emit a signal if something <a href="https://lazka.github.io/pgi-docs/#Soup-2.4/classes/WebsocketConnection.html%23Soup.WebsocketConnection.signals.error">goes wrong</a>.</p>
<p><img src="https://dev-to-uploads.s3.amazonaws.com/i/6th5fr8cow67bmb8nh3z.gif" alt="Spacebeam"></p>
<h2 id="conclusion">Conclusion</h2>
<p>Simple yet descriptive example of how to start building your GTK+ app with WebSockets inside. Maybe you will create a new chat app?</p>
<p>Stay tuned if you want to read more! Or go to the docs from the Links section.</p>
<p>Anyway, good luck!</p>
<h2 id="links">Links</h2>
<p>Source code:
<a href="https://github.com/amka/pygobject-gtk-websocket-app">https://github.com/amka/pygobject-gtk-websocket-app</a></p>
<p>Docs:</p>
<ul>
<li><a href="https://websocket.org/">https://websocket.org/</a></li>
<li><a href="https://lazka.github.io/pgi-docs/#Gtk-3.0">https://lazka.github.io/pgi-docs/#Gtk-3.0</a></li>
<li><a href="https://lazka.github.io/pgi-docs/#Soup-2.4">https://lazka.github.io/pgi-docs/#Soup-2.4</a></li>
</ul>
        ]]></turbo:content></item><item turbo="true"><turbo:extendedHtml>true</turbo:extendedHtml><title>Norka</title><link>https://tenderowl.com/work/norka/</link><pubDate>Fri, 18 Dec 2020 11:26:21 +0300</pubDate><guid>https://tenderowl.com/work/norka/</guid><description>&lt;div class="row">
&lt;div class="col">
&lt;div class="text-center">
&lt;img src="https://tenderowl.com/work/images/editor-mix.png" alt="">
&lt;/div>
&lt;/div>
&lt;/div>
&lt;div class="row">
&lt;div class="col col-6 col-4-md text-center">
&lt;h3 class="text-xl text-center mt-8">Markdown&lt;/h3>
&lt;img src="https://tenderowl.com/work/images/markup.png" class="mx-auto my-2 rounded-md hoover" />
&lt;/div>
&lt;div class="col col-6 col-4-md text-center">
&lt;h3 class="text-xl text-center mt-8">Reading time&lt;/h3>
&lt;img src="https://tenderowl.com/work/images/reading-time.png" class="mx-auto my-2 rounded-md hoover" />
&lt;/div>
&lt;div class="col col-6 col-4-md text-center">
&lt;h3 class="text-xl text-center mt-8">Preview content&lt;/h3>
&lt;img src="https://tenderowl.com/work/images/preview-grid.png" class="mx-auto my-2 rounded-md hoover" />
&lt;/div>
&lt;div class="col col-6 col-4-md text-center">
&lt;h3 class="text-xl text-center mt-8">Export&lt;/h3>
&lt;img src="https://tenderowl.com/work/images/export-menu.png" class="mx-auto my-2 rounded-md hoover" />
&lt;/div>
&lt;div class="col col-6 col-4-md text-center">
&lt;h3 class="text-xl text-center mt-8">Spell checking&lt;/h3>
&lt;img src="https://tenderowl.com/work/images/spellcheck.png" class="mx-auto my-2 rounded-md hoover" />
&lt;/div>
&lt;div class="col col-6 col-4-md text-center">
&lt;h3 class="text-xl text-center mt-8">Color schemes&lt;/h3>
&lt;img src="https://tenderowl.com/work/images/schemes.png" class="mx-auto my-2 rounded-md hoover" />
&lt;/div>
&lt;/div>
&lt;div class="row">
&lt;div class="col">
&lt;ul class="list-disc pl-8 pb-4">
&lt;li>Text search&lt;/li>
&lt;li>Autosave&lt;/li>
&lt;li>Drag-n-drop import local files&lt;/li>
&lt;li>Export to files&lt;/li>
&lt;li>Export to Medium.com&lt;/li>
&lt;li>Export to Write.as&lt;/li>
&lt;li>Different color schemes for editor&lt;/li>
&lt;li>Document archiving&lt;/li>
&lt;li>And of course, you can delete them permanently&lt;/li>
&lt;/ul>
&lt;h3 class="text-4xl amatic">More to come!&lt;/h3>
&lt;/div>
&lt;/div>
&lt;div class="row">
&lt;div class="col">
&lt;h2 class="text-6xl amatic mb-10">Get Norka&lt;/h2>
&lt;div class=" mb-4">
&lt;div class="text-center items-center">
&lt;a href="https://snapcraft.io/norka" rel="nofollow"
onclick="ym(48108788,'reachGoal','snapstore')" class="mx-5">&lt;img height="50"
alt="Get it from the Snap Store"
src="https://snapcraft.io/static/images/badges/en/snap-store-black.svg"
style="height: 50px;">&lt;/a>
&lt;a href="https://flathub.org/apps/details/com.github.tenderowl.norka" rel="nofollow"
onclick="ym(48108788,'reachGoal','flathub')" class="mx-5">&lt;img height="50"
alt="Download on Flathub" src="https://flathub.org/assets/badges/flathub-badge-en.png"
style="height: 50px;">&lt;/a>
&lt;a href="https://appcenter.elementary.io/com.github.tenderowl.norka"
onclick="ym(48108788,'reachGoal','appcenter')">&lt;img
src="https://appcenter.elementary.io/badge.svg" alt="Get it on AppCenter"
style="height: 50px;">&lt;/a>
&lt;/div>
&lt;/div>
&lt;p class="py-8">
If you feel enough power to contribute or fork feel free to do it. Source code
&lt;a href="https://github.com/TenderOwl/Norka/" title="" class="px-1">
&lt;svg width="20" height="19" fill="currentColor" xmlns="http://www.w3.org/2000/svg" class="inline">
&lt;path d="M10 0c1.814 0 3.487.435 5.02 1.306a9.827 9.827 0 0 1 3.639 3.542A9.33 9.33 0 0 1 20 9.734c0 2.121-.636 4.03-1.908 5.723a9.783 9.783 0 0 1-4.928 3.518c-.234.042-.408.012-.52-.09a.49.49 0 0 1-.17-.38l.006-.969c.005-.621.007-1.19.007-1.705 0-.82-.226-1.42-.677-1.8.495-.05.94-.126 1.335-.228a5.4 5.4 0 0 0 1.223-.494 3.62 3.62 0 0 0 1.055-.843c.282-.334.512-.777.69-1.33.178-.554.267-1.19.267-1.909a3.7 3.7 0 0 0-1.028-2.61c.32-.77.286-1.631-.105-2.586-.243-.076-.594-.03-1.054.14-.46.168-.86.354-1.198.557l-.495.304a9.478 9.478 0 0 0-2.5-.33c-.86 0-1.693.11-2.5.33a11.6 11.6 0 0 0-.553-.342c-.23-.135-.593-.298-1.088-.488-.494-.19-.863-.247-1.106-.171-.391.955-.426 1.816-.105 2.585A3.7 3.7 0 0 0 3.62 9.227c0 .719.089 1.352.267 1.902.178.549.406.993.683 1.33.278.339.627.622 1.048.85a5.4 5.4 0 0 0 1.224.494c.395.102.84.178 1.335.228-.338.305-.551.74-.638 1.306a2.631 2.631 0 0 1-.586.19 3.782 3.782 0 0 1-.742.063c-.287 0-.57-.09-.853-.272a2.256 2.256 0 0 1-.723-.792 2.068 2.068 0 0 0-.631-.66c-.256-.168-.471-.27-.645-.304l-.26-.038c-.182 0-.308.02-.378.057-.07.038-.09.087-.065.146.026.06.065.118.117.178.053.059.109.11.17.152l.09.063c.192.085.38.245.567.482.187.236.324.452.41.646l.13.292c.113.32.304.58.574.78.269.198.56.325.872.38.312.054.614.084.905.088.29.004.532-.01.723-.044l.299-.05c0 .32.002.694.007 1.12l.006.692a.49.49 0 0 1-.17.38c-.112.101-.286.13-.52.089a9.783 9.783 0 0 1-4.928-3.518C.636 13.763 0 11.855 0 9.734a9.33 9.33 0 0 1 1.341-4.886 9.827 9.827 0 0 1 3.64-3.542C6.512.436 8.185 0 10 0zM3.79 13.98c.025-.058-.005-.11-.092-.151-.087-.026-.143-.017-.17.025-.025.06.005.11.092.152.078.05.134.042.17-.025zm.403.432c.06-.043.052-.11-.026-.203-.087-.076-.157-.089-.209-.038-.06.042-.052.11.026.203.087.084.157.097.209.038zm.39.57c.078-.06.078-.14 0-.24-.07-.11-.143-.136-.221-.077-.078.042-.078.118 0 .228.078.11.152.14.221.089zm.547.532c.07-.067.052-.148-.052-.24-.104-.102-.19-.115-.26-.039-.078.068-.061.148.052.241.104.102.19.114.26.038zm.742.317c.026-.093-.03-.16-.169-.203-.13-.033-.213-.004-.247.09-.035.092.021.155.169.19.13.05.213.025.247-.077zm.82.064c0-.11-.073-.157-.22-.14-.14 0-.209.047-.209.14 0 .11.074.156.221.139.14 0 .209-.046.209-.14zm.756-.127c-.017-.093-.096-.131-.234-.114-.14.025-.2.088-.183.19.018.101.096.135.235.101.139-.034.2-.093.182-.177z" fill-rule="nonzero">&lt;/path>
&lt;/svg>
Available on GitHub
&lt;/a>
&lt;/p></description><turbo:content><![CDATA[
            <div class="row">
    <div class="col">
        <div class="text-center">
            <img src="/work/images/editor-mix.png" alt="">
        </div>
    </div>
</div>
<div class="row">
    <div class="col col-6 col-4-md text-center">
        <h3 class="text-xl text-center mt-8">Markdown</h3>
        <img src="/work/images/markup.png" class="mx-auto my-2 rounded-md hoover" />
    </div>
    <div class="col col-6 col-4-md text-center">
        <h3 class="text-xl text-center mt-8">Reading time</h3>
        <img src="/work/images/reading-time.png" class="mx-auto my-2 rounded-md hoover" />
    </div>
    <div class="col col-6 col-4-md text-center">
        <h3 class="text-xl text-center mt-8">Preview content</h3>
        <img src="/work/images/preview-grid.png" class="mx-auto my-2 rounded-md hoover" />
    </div>
    <div class="col col-6 col-4-md text-center">
        <h3 class="text-xl text-center mt-8">Export</h3>
        <img src="/work/images/export-menu.png" class="mx-auto my-2 rounded-md hoover" />
    </div>
    <div class="col col-6 col-4-md text-center">
        <h3 class="text-xl text-center mt-8">Spell checking</h3>
        <img src="/work/images/spellcheck.png" class="mx-auto my-2 rounded-md hoover" />
    </div>
    <div class="col col-6 col-4-md text-center">
        <h3 class="text-xl text-center mt-8">Color schemes</h3>
        <img src="/work/images/schemes.png" class="mx-auto my-2 rounded-md hoover" />
    </div>
</div>
<div class="row">
    <div class="col">
        <ul class="list-disc pl-8 pb-4">
            <li>Text search</li>
            <li>Autosave</li>
            <li>Drag-n-drop import local files</li>
            <li>Export to files</li>
            <li>Export to Medium.com</li>
            <li>Export to Write.as</li>
            <li>Different color schemes for editor</li>
            <li>Document archiving</li>
            <li>And of course, you can delete them permanently</li>
        </ul>
        <h3 class="text-4xl amatic">More to come!</h3>
    </div>
</div>
<div class="row">
    <div class="col">
        <h2 class="text-6xl amatic mb-10">Get Norka</h2>
        <div class=" mb-4">
            <div class="text-center items-center">
                <a href="https://snapcraft.io/norka" rel="nofollow"
                    onclick="ym(48108788,'reachGoal','snapstore')" class="mx-5"><img height="50"
                        alt="Get it from the Snap Store"
                        src="https://snapcraft.io/static/images/badges/en/snap-store-black.svg"
                        style="height: 50px;"></a>
                <a href="https://flathub.org/apps/details/com.github.tenderowl.norka" rel="nofollow"
                    onclick="ym(48108788,'reachGoal','flathub')" class="mx-5"><img height="50"
                        alt="Download on Flathub" src="https://flathub.org/assets/badges/flathub-badge-en.png"
                        style="height: 50px;"></a>
                <a href="https://appcenter.elementary.io/com.github.tenderowl.norka"
                    onclick="ym(48108788,'reachGoal','appcenter')"><img
                        src="https://appcenter.elementary.io/badge.svg" alt="Get it on AppCenter"
                        style="height: 50px;"></a>
            </div>
        </div>
        <p class="py-8">
            If you feel enough power to contribute or fork feel free to do it. Source code
            <a href="https://github.com/TenderOwl/Norka/" title="" class="px-1">
            <svg width="20" height="19" fill="currentColor" xmlns="http://www.w3.org/2000/svg" class="inline">
                    <path d="M10 0c1.814 0 3.487.435 5.02 1.306a9.827 9.827 0 0 1 3.639 3.542A9.33 9.33 0 0 1 20 9.734c0 2.121-.636 4.03-1.908 5.723a9.783 9.783 0 0 1-4.928 3.518c-.234.042-.408.012-.52-.09a.49.49 0 0 1-.17-.38l.006-.969c.005-.621.007-1.19.007-1.705 0-.82-.226-1.42-.677-1.8.495-.05.94-.126 1.335-.228a5.4 5.4 0 0 0 1.223-.494 3.62 3.62 0 0 0 1.055-.843c.282-.334.512-.777.69-1.33.178-.554.267-1.19.267-1.909a3.7 3.7 0 0 0-1.028-2.61c.32-.77.286-1.631-.105-2.586-.243-.076-.594-.03-1.054.14-.46.168-.86.354-1.198.557l-.495.304a9.478 9.478 0 0 0-2.5-.33c-.86 0-1.693.11-2.5.33a11.6 11.6 0 0 0-.553-.342c-.23-.135-.593-.298-1.088-.488-.494-.19-.863-.247-1.106-.171-.391.955-.426 1.816-.105 2.585A3.7 3.7 0 0 0 3.62 9.227c0 .719.089 1.352.267 1.902.178.549.406.993.683 1.33.278.339.627.622 1.048.85a5.4 5.4 0 0 0 1.224.494c.395.102.84.178 1.335.228-.338.305-.551.74-.638 1.306a2.631 2.631 0 0 1-.586.19 3.782 3.782 0 0 1-.742.063c-.287 0-.57-.09-.853-.272a2.256 2.256 0 0 1-.723-.792 2.068 2.068 0 0 0-.631-.66c-.256-.168-.471-.27-.645-.304l-.26-.038c-.182 0-.308.02-.378.057-.07.038-.09.087-.065.146.026.06.065.118.117.178.053.059.109.11.17.152l.09.063c.192.085.38.245.567.482.187.236.324.452.41.646l.13.292c.113.32.304.58.574.78.269.198.56.325.872.38.312.054.614.084.905.088.29.004.532-.01.723-.044l.299-.05c0 .32.002.694.007 1.12l.006.692a.49.49 0 0 1-.17.38c-.112.101-.286.13-.52.089a9.783 9.783 0 0 1-4.928-3.518C.636 13.763 0 11.855 0 9.734a9.33 9.33 0 0 1 1.341-4.886 9.827 9.827 0 0 1 3.64-3.542C6.512.436 8.185 0 10 0zM3.79 13.98c.025-.058-.005-.11-.092-.151-.087-.026-.143-.017-.17.025-.025.06.005.11.092.152.078.05.134.042.17-.025zm.403.432c.06-.043.052-.11-.026-.203-.087-.076-.157-.089-.209-.038-.06.042-.052.11.026.203.087.084.157.097.209.038zm.39.57c.078-.06.078-.14 0-.24-.07-.11-.143-.136-.221-.077-.078.042-.078.118 0 .228.078.11.152.14.221.089zm.547.532c.07-.067.052-.148-.052-.24-.104-.102-.19-.115-.26-.039-.078.068-.061.148.052.241.104.102.19.114.26.038zm.742.317c.026-.093-.03-.16-.169-.203-.13-.033-.213-.004-.247.09-.035.092.021.155.169.19.13.05.213.025.247-.077zm.82.064c0-.11-.073-.157-.22-.14-.14 0-.209.047-.209.14 0 .11.074.156.221.139.14 0 .209-.046.209-.14zm.756-.127c-.017-.093-.096-.131-.234-.114-.14.025-.2.088-.183.19.018.101.096.135.235.101.139-.034.2-.093.182-.177z" fill-rule="nonzero"></path>
                </svg>
                Available on GitHub
            </a>
        </p>
    </div>
</div>
</div>

        ]]></turbo:content></item><item turbo="true"><turbo:extendedHtml>true</turbo:extendedHtml><title>Server microframework for the Dart language</title><link>https://tenderowl.com/work/laska/</link><pubDate>Fri, 18 Dec 2020 11:26:21 +0300</pubDate><guid>https://tenderowl.com/work/laska/</guid><description>&lt;p>Laska is a server-side microframework for Dart.
Currently, in development, not for production use.&lt;/p></description><turbo:content><![CDATA[
            <p>Laska is a server-side microframework for Dart.
Currently, in development, not for production use.</p>
<h2 id="features">Features</h2>
<ul>
<li><input checked="" disabled="" type="checkbox" /> Dynamic routing with placeholders and wildcards</li>
<li><input checked="" disabled="" type="checkbox" /> Concurrency via Isolates</li>
<li><input checked="" disabled="" type="checkbox" /> Extensible Middleware support</li>
<li><input disabled="" type="checkbox" /> Template rendering</li>
<li><input disabled="" type="checkbox" /> Logging</li>
</ul>
<h2 id="how-to-use-it">How to use it</h2>
<div class="rounded"><pre tabindex="0" class="chroma"><code class="language-dart" data-lang="dart"><span class="line"><span class="cl"><span class="k">import</span> <span class="s1">&#39;dart:io&#39;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">import</span> <span class="s1">&#39;package:laska/laska.dart&#39;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kt">void</span> <span class="n">main</span><span class="p">()</span> <span class="kd">async</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="c1">// Create new Laska object with 2 [Isolate]
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>  <span class="kd">final</span> <span class="n">laska</span> <span class="o">=</span> <span class="n">Laska</span><span class="p">(</span><span class="nl">isolateCount:</span> <span class="m">2</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="c1">// Set global BasicAuth middleware 
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>  <span class="n">laska</span><span class="p">.</span><span class="n">Use</span><span class="p">(</span><span class="n">BasicAuth</span><span class="p">(</span><span class="s1">&#39;laska&#39;</span><span class="p">,</span> <span class="s1">&#39;ermine&#39;</span><span class="p">,</span> <span class="nl">realm:</span> <span class="s1">&#39;Access to private zone&#39;</span><span class="p">));</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="n">laska</span><span class="p">.</span><span class="n">GET</span><span class="p">(</span><span class="s1">&#39;/users/:userId&#39;</span><span class="p">,</span> <span class="n">getUserById</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">  <span class="n">laska</span><span class="p">.</span><span class="n">POST</span><span class="p">(</span><span class="s1">&#39;/users/&#39;</span><span class="p">,</span> <span class="n">createUser</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="c1">// Start server
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>  <span class="kd">await</span> <span class="n">run</span><span class="p">(</span><span class="n">laska</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kt">void</span> <span class="n">getUserById</span><span class="p">(</span><span class="n">Context</span> <span class="n">context</span><span class="p">)</span> <span class="kd">async</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="kd">await</span> <span class="n">context</span><span class="p">.</span><span class="n">HTML</span><span class="p">(</span><span class="s1">&#39;User: &lt;b&gt;</span><span class="si">${</span><span class="n">context</span><span class="p">.</span><span class="n">params</span><span class="p">[</span><span class="s1">&#39;userId&#39;</span><span class="p">]</span><span class="si">}</span><span class="s1">&lt;/b&gt;&#39;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kt">void</span> <span class="n">createUser</span><span class="p">(</span><span class="n">Context</span> <span class="n">context</span><span class="p">)</span> <span class="kd">async</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="kd">await</span> <span class="n">context</span><span class="p">.</span><span class="n">JSON</span><span class="p">({</span><span class="s1">&#39;status&#39;</span><span class="o">:</span> <span class="s1">&#39;created&#39;</span><span class="p">},</span> <span class="nl">statusCode:</span> <span class="n">HttpStatus</span><span class="p">.</span><span class="n">created</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div>
        ]]></turbo:content></item><item turbo="true"><turbo:extendedHtml>true</turbo:extendedHtml><title>Frog</title><link>https://tenderowl.com/work/frog/</link><pubDate>Sun, 16 Aug 2020 20:26:21 +0300</pubDate><guid>https://tenderowl.com/work/frog/</guid><description>&lt;div class="row">
&lt;div class="col">
&lt;div class="text-center">
&lt;img src="https://tenderowl.com/work/images/frog/frog-screenshot.png" alt="Frog window">
&lt;/div>
&lt;/div>
&lt;/div>
&lt;h2 id="extract-text-from-any-source">Extract Text From Any source&lt;/h2>
&lt;p>Quickly extract non-selectable text from anywhere: videos, PDFs, screencasts, webpages, photos, etc. It&amp;rsquo;s so simple and easy as taking a screenshot with a built-in snipping tool for Mac..&lt;/p></description><turbo:content><![CDATA[
            <div class="row">
    <div class="col">
        <div class="text-center">
            <img src="/work/images/frog/frog-screenshot.png" alt="Frog window">
        </div>
    </div>
</div>
<h2 id="extract-text-from-any-source">Extract Text From Any source</h2>
<p>Quickly extract non-selectable text from anywhere: videos, PDFs, screencasts, webpages, photos, etc. It&rsquo;s so simple and easy as taking a screenshot with a built-in snipping tool for Mac..</p>
<h2 id="see-it-in-action">See it in action</h2>
<div class="row">
    <div class="col">
        <div class="text-center">
              <iframe width="560" height="315" src="https://www.youtube.com/embed/lWpsT5uT6To" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
          </div>
    </div>
</div>
<div class="row">
    <div class="col">
        <h2 class="text-6xl amatic mb-10">Get Frog</h2>
        <div class=" mb-4">
            <a href="https://appcenter.elementary.io/com.github.tenderowl.frog"
                    onclick="ym(48108788,'reachGoal','appcenter')"><img
                        src="https://appcenter.elementary.io/badge.svg" alt="Get it on AppCenter"
                        style="height: 50px;"></a>
        </div>
        <p class="py-8">
            If you feel enough power to contribute or fork feel free to do it. Source code
            <a href="https://github.com/TenderOwl/Frog/" title="" class="px-1">
            <svg width="20" height="19" fill="currentColor" xmlns="http://www.w3.org/2000/svg" class="inline">
                    <path d="M10 0c1.814 0 3.487.435 5.02 1.306a9.827 9.827 0 0 1 3.639 3.542A9.33 9.33 0 0 1 20 9.734c0 2.121-.636 4.03-1.908 5.723a9.783 9.783 0 0 1-4.928 3.518c-.234.042-.408.012-.52-.09a.49.49 0 0 1-.17-.38l.006-.969c.005-.621.007-1.19.007-1.705 0-.82-.226-1.42-.677-1.8.495-.05.94-.126 1.335-.228a5.4 5.4 0 0 0 1.223-.494 3.62 3.62 0 0 0 1.055-.843c.282-.334.512-.777.69-1.33.178-.554.267-1.19.267-1.909a3.7 3.7 0 0 0-1.028-2.61c.32-.77.286-1.631-.105-2.586-.243-.076-.594-.03-1.054.14-.46.168-.86.354-1.198.557l-.495.304a9.478 9.478 0 0 0-2.5-.33c-.86 0-1.693.11-2.5.33a11.6 11.6 0 0 0-.553-.342c-.23-.135-.593-.298-1.088-.488-.494-.19-.863-.247-1.106-.171-.391.955-.426 1.816-.105 2.585A3.7 3.7 0 0 0 3.62 9.227c0 .719.089 1.352.267 1.902.178.549.406.993.683 1.33.278.339.627.622 1.048.85a5.4 5.4 0 0 0 1.224.494c.395.102.84.178 1.335.228-.338.305-.551.74-.638 1.306a2.631 2.631 0 0 1-.586.19 3.782 3.782 0 0 1-.742.063c-.287 0-.57-.09-.853-.272a2.256 2.256 0 0 1-.723-.792 2.068 2.068 0 0 0-.631-.66c-.256-.168-.471-.27-.645-.304l-.26-.038c-.182 0-.308.02-.378.057-.07.038-.09.087-.065.146.026.06.065.118.117.178.053.059.109.11.17.152l.09.063c.192.085.38.245.567.482.187.236.324.452.41.646l.13.292c.113.32.304.58.574.78.269.198.56.325.872.38.312.054.614.084.905.088.29.004.532-.01.723-.044l.299-.05c0 .32.002.694.007 1.12l.006.692a.49.49 0 0 1-.17.38c-.112.101-.286.13-.52.089a9.783 9.783 0 0 1-4.928-3.518C.636 13.763 0 11.855 0 9.734a9.33 9.33 0 0 1 1.341-4.886 9.827 9.827 0 0 1 3.64-3.542C6.512.436 8.185 0 10 0zM3.79 13.98c.025-.058-.005-.11-.092-.151-.087-.026-.143-.017-.17.025-.025.06.005.11.092.152.078.05.134.042.17-.025zm.403.432c.06-.043.052-.11-.026-.203-.087-.076-.157-.089-.209-.038-.06.042-.052.11.026.203.087.084.157.097.209.038zm.39.57c.078-.06.078-.14 0-.24-.07-.11-.143-.136-.221-.077-.078.042-.078.118 0 .228.078.11.152.14.221.089zm.547.532c.07-.067.052-.148-.052-.24-.104-.102-.19-.115-.26-.039-.078.068-.061.148.052.241.104.102.19.114.26.038zm.742.317c.026-.093-.03-.16-.169-.203-.13-.033-.213-.004-.247.09-.035.092.021.155.169.19.13.05.213.025.247-.077zm.82.064c0-.11-.073-.157-.22-.14-.14 0-.209.047-.209.14 0 .11.074.156.221.139.14 0 .209-.046.209-.14zm.756-.127c-.017-.093-.096-.131-.234-.114-.14.025-.2.088-.183.19.018.101.096.135.235.101.139-.034.2-.093.182-.177z" fill-rule="nonzero"></path>
                </svg>
                Available on GitHub
            </a>
        </p>
    </div>
</div>
</div>
        ]]></turbo:content></item></channel></rss>