<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Benito Mallamaci</title>
    <description>The latest articles on DEV Community by Benito Mallamaci (@benito_mallamaci_c902e934).</description>
    <link>https://dev.to/benito_mallamaci_c902e934</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3795503%2F630a75db-2509-45e5-aa9e-ef055da7ceb4.png</url>
      <title>DEV Community: Benito Mallamaci</title>
      <link>https://dev.to/benito_mallamaci_c902e934</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/benito_mallamaci_c902e934"/>
    <language>en</language>
    <item>
      <title>I Made Tkinter Look Like a Modern Glassmorphic App — Here's the Dark Magic I Used</title>
      <dc:creator>Benito Mallamaci</dc:creator>
      <pubDate>Thu, 26 Feb 2026 22:29:53 +0000</pubDate>
      <link>https://dev.to/benito_mallamaci_c902e934/i-made-tkinter-look-like-a-modern-glassmorphic-app-heres-the-dark-magic-i-used-3718</link>
      <guid>https://dev.to/benito_mallamaci_c902e934/i-made-tkinter-look-like-a-modern-glassmorphic-app-heres-the-dark-magic-i-used-3718</guid>
      <description>&lt;p&gt;If you've ever built a desktop app in Python, you've probably used &lt;strong&gt;Tkinter&lt;/strong&gt;. And if you have, you probably think it's doomed to look like a clunky, grey Windows 95 application.&lt;/p&gt;

&lt;p&gt;I thought so too.&lt;/p&gt;

&lt;p&gt;But recently, I needed a lightweight, floating UI for a &lt;strong&gt;100% local, offline voice assistant&lt;/strong&gt; I was building. I absolutely refused to bundle an entire Chromium instance (Electron) just to render a small widget.&lt;/p&gt;

&lt;p&gt;So I decided to push Tkinter to its absolute limits. The result is &lt;a href="https://github.com/benmaster82/writher" rel="noopener noreferrer"&gt;&lt;strong&gt;Writher&lt;/strong&gt;&lt;/a&gt;: an open-source, privacy-first voice assistant and dictation tool powered by &lt;code&gt;faster-whisper&lt;/code&gt; and &lt;code&gt;Ollama&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;In this post, I'll break down the tricks I used to make a legacy Python GUI look modern, and the architecture behind a fully local AI desktop app.&lt;/p&gt;




&lt;h2&gt;
  
  
  🎨 The UI Hack: Making Tkinter Beautiful
&lt;/h2&gt;

&lt;p&gt;To get that modern, &lt;strong&gt;glassmorphic floating pill shape&lt;/strong&gt; with glowing borders, I completely bypassed Tkinter's standard widgets.&lt;/p&gt;

&lt;p&gt;Instead, I used &lt;strong&gt;PIL (Pillow)&lt;/strong&gt; to render high-resolution graphics dynamically on a transparent Tkinter &lt;code&gt;Canvas&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Trick: Borderless &amp;amp; Transparent Windows
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Remove the window frame entirely
&lt;/span&gt;&lt;span class="n"&gt;root&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;overrideredirect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Chromakey hack: make a specific color fully transparent
&lt;/span&gt;&lt;span class="n"&gt;root&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;wm_attributes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;-transparentcolor&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;#000001&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This gives you a &lt;strong&gt;frameless, floating window&lt;/strong&gt; — the foundation for any modern-looking widget.&lt;/p&gt;

&lt;h3&gt;
  
  
  Dynamic Glow Rendering
&lt;/h3&gt;

&lt;p&gt;Every frame of the animation (the bot's eyes changing expressions: &lt;em&gt;listening&lt;/em&gt;, &lt;em&gt;thinking&lt;/em&gt;, &lt;em&gt;happy&lt;/em&gt;) is drawn on-the-fly using &lt;code&gt;ImageDraw&lt;/code&gt; and &lt;code&gt;ImageFilter.GaussianBlur&lt;/code&gt; to create a glowing effect that mimics SVG filters:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;PIL&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Image&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ImageDraw&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ImageFilter&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;draw_glow&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;size&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;color&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;blur_radius&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;img&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Image&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;RGBA&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;size&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="n"&gt;draw&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ImageDraw&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Draw&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;img&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;draw&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;rounded_rectangle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;blur_radius&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;blur_radius&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
         &lt;span class="n"&gt;size&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;blur_radius&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;size&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;blur_radius&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="n"&gt;radius&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;fill&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;color&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;img&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ImageFilter&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;GaussianBlur&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;blur_radius&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  The Ghost Window
&lt;/h3&gt;

&lt;p&gt;Since Writher is a dictation tool, clicking it &lt;strong&gt;shouldn't steal focus&lt;/strong&gt; from your active app (like VSCode or a text editor). I used Win32 &lt;code&gt;ctypes&lt;/code&gt; to apply the &lt;code&gt;WS_EX_NOACTIVATE&lt;/code&gt; style:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;ctypes&lt;/span&gt;

&lt;span class="n"&gt;GWL_EXSTYLE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;20&lt;/span&gt;
&lt;span class="n"&gt;WS_EX_NOACTIVATE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mh"&gt;0x08000000&lt;/span&gt;
&lt;span class="n"&gt;WS_EX_TOOLWINDOW&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mh"&gt;0x00000080&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;make_ghost_window&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;hwnd&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;style&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ctypes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;windll&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;user32&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;GetWindowLongW&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;hwnd&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;GWL_EXSTYLE&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;ctypes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;windll&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;user32&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;SetWindowLongW&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;hwnd&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;GWL_EXSTYLE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;style&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;WS_EX_NOACTIVATE&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;WS_EX_TOOLWINDOW&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;💡 Pro tip:&lt;/strong&gt; &lt;code&gt;WS_EX_TOOLWINDOW&lt;/code&gt; also hides the window from the Alt+Tab menu, making it behave like a true desktop widget.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  🧠 The Brain: 100% Local AI
&lt;/h2&gt;

&lt;p&gt;I'm tired of sending my voice and private notes to the cloud. Writher operates &lt;strong&gt;entirely on your local machine&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Speech-to-Text&lt;/strong&gt; — I used &lt;a href="https://github.com/SYSTRAN/faster-whisper" rel="noopener noreferrer"&gt;&lt;code&gt;faster-whisper&lt;/code&gt;&lt;/a&gt; (CTranslate2). It runs flawlessly on CPU or GPU and transcribes voice in near real-time.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The LLM&lt;/strong&gt; — I hooked the app to &lt;a href="https://ollama.ai" rel="noopener noreferrer"&gt;Ollama&lt;/a&gt;. Press &lt;code&gt;Ctrl+R&lt;/code&gt;, Writher listens, transcribes, and passes the text to a local model for processing.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Function Calling&lt;/strong&gt; — This is where it gets interesting. Instead of just chatting, I configured the LLM with &lt;strong&gt;tool definitions&lt;/strong&gt;. It converts your voice commands into structured function calls — saving notes, scheduling appointments, or setting reminders in a local &lt;strong&gt;SQLite&lt;/strong&gt; database:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Thread-safe SQLite with WAL mode
&lt;/span&gt;&lt;span class="n"&gt;conn&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;sqlite3&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;connect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;writher.db&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;conn&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;execute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;PRAGMA journal_mode=WAL&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The LLM doesn't just understand you — it &lt;em&gt;acts&lt;/em&gt; on what you say.&lt;/p&gt;




&lt;h2&gt;
  
  
  ⚙️ The Muscle: Seamless OS Integration
&lt;/h2&gt;

&lt;p&gt;Most dictation tools copy text to your clipboard and force you to paste manually. Writher acts like a &lt;strong&gt;phantom keyboard&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;I initially used &lt;code&gt;pyperclip&lt;/code&gt;, but it suffers from race conditions when other apps lock the clipboard. To make it bulletproof, I wrote a &lt;strong&gt;custom clipboard injector&lt;/strong&gt; using the Win32 API (&lt;code&gt;OpenClipboard&lt;/code&gt;, &lt;code&gt;SetClipboardData&lt;/code&gt;).&lt;/p&gt;

&lt;p&gt;The flow:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Save&lt;/strong&gt; your current clipboard contents&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Inject&lt;/strong&gt; the transcribed text and simulate &lt;code&gt;Ctrl+V&lt;/code&gt; via &lt;code&gt;pynput&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Restore&lt;/strong&gt; your original clipboard — all in milliseconds&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;And just in case Windows blocks the clipboard? I implemented a fail-safe that automatically appends your dictation to a &lt;code&gt;recovery_notes.txt&lt;/code&gt; file. &lt;strong&gt;You never lose a single word.&lt;/strong&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  🔑 What I Learned
&lt;/h2&gt;

&lt;p&gt;Building Writher taught me a few things I wasn't expecting:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;You don't need Electron for everything.&lt;/strong&gt; A transparent Tkinter canvas + Pillow can produce surprisingly polished UIs. The binary is tiny compared to any web-based alternative.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Local AI is ready.&lt;/strong&gt; With &lt;code&gt;faster-whisper&lt;/code&gt; + Ollama, you can build genuinely useful AI tools that never phone home. Privacy doesn't have to mean sacrificing quality.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Win32 APIs are your secret weapon on Windows.&lt;/strong&gt; Ghost windows, clipboard control, focus management — they unlock capabilities that pure Python can't reach alone.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  🚀 Try It Yourself
&lt;/h2&gt;

&lt;p&gt;The whole project is open-source. You can check out the UI implementation, the AI pipeline, and run it on your own machine:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;👉 &lt;a href="https://github.com/benmaster82/writher" rel="noopener noreferrer"&gt;Writher on GitHub&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I'd love to hear your thoughts. Have you ever pushed a legacy GUI framework beyond what it was meant to do? What local LLM setup are you running? Drop a comment — I read all of them.&lt;/p&gt;

&lt;p&gt;And if you find the project useful, a ⭐ on the repo helps more than you'd think. 🙏&lt;/p&gt;

</description>
      <category>python</category>
      <category>opensource</category>
      <category>ai</category>
      <category>tutorial</category>
    </item>
  </channel>
</rss>
