<?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: h13ris</title>
    <description>The latest articles on DEV Community by h13ris (@hi3ris).</description>
    <link>https://dev.to/hi3ris</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%2F3150332%2Fc3fe7bd4-db7d-4d45-b300-1f1938b58730.jpeg</url>
      <title>DEV Community: h13ris</title>
      <link>https://dev.to/hi3ris</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/hi3ris"/>
    <language>en</language>
    <item>
      <title>PyQt6: how to ship Python GUIs that don't look like 1998</title>
      <dc:creator>h13ris</dc:creator>
      <pubDate>Mon, 04 May 2026 18:56:36 +0000</pubDate>
      <link>https://dev.to/hi3ris/pyqt6-how-to-ship-python-guis-that-dont-look-like-1998-2952</link>
      <guid>https://dev.to/hi3ris/pyqt6-how-to-ship-python-guis-that-dont-look-like-1998-2952</guid>
      <description>&lt;p&gt;I have a confession. For years, when a developer proudly showed me their Python app — gray square buttons, a &lt;code&gt;Listbox&lt;/code&gt; straight out of 1998 — I would politely nod. I've stopped doing that.&lt;/p&gt;

&lt;p&gt;Not because I turned mean. Because &lt;strong&gt;PyQt6 exists&lt;/strong&gt;, and there's no excuse anymore.&lt;/p&gt;

&lt;p&gt;This article is my attempt to convince you — yes, you, the one still typing &lt;code&gt;import tkinter&lt;/code&gt; out of habit — that something radically better is sitting one &lt;code&gt;pip install&lt;/code&gt; away. I'll walk you through side-by-side comparisons and real snippets from a project I've been building for months: &lt;strong&gt;WatchTower&lt;/strong&gt;, a website defacement monitoring system written entirely in PyQt6.&lt;/p&gt;

&lt;p&gt;Spoiler: by the end, you'll want to rewrite everything you ever shipped in Tkinter.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4xmfrpjp213a0tkr1pqo.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4xmfrpjp213a0tkr1pqo.png" alt="WatchTower main dashboard built with PyQt6 — KPI cards, history chart, sites table, and side panel" width="605" height="340"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The Tkinter problem in three lines
&lt;/h2&gt;

&lt;p&gt;Let's be honest. Tkinter ships with Python, it's free, it's documented, and it works. That's the whole pitch. The rest is masochism.&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;# Tkinter — a "dashboard" that hurts to look at
&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;tkinter&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;tk&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;tkinter&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;ttk&lt;/span&gt;

&lt;span class="n"&gt;root&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;tk&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Tk&lt;/span&gt;&lt;span class="p"&gt;()&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;title&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;My fancy tool&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&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;geometry&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;400x300&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;label&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;tk&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Label&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;root&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Status: OK&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;bg&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;grey&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;fg&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;white&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;label&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;pack&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pady&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;button&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;tk&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Button&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;root&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Scan&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;button&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;pack&lt;/span&gt;&lt;span class="p"&gt;()&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;mainloop&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You get something gray, flat, screaming "1998" at the top of its lungs. Want to change the font? Fight with &lt;code&gt;font=("Arial", 10)&lt;/code&gt;. Want a dark theme? Good luck. Want a real-time chart? Cram &lt;code&gt;matplotlib&lt;/code&gt; in there and pray.&lt;/p&gt;

&lt;h2&gt;
  
  
  The same thing in PyQt6
&lt;/h2&gt;

&lt;p&gt;Now look at the equivalent in PyQt6:&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;# PyQt6 — clean, modern, stylable
&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;sys&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;PyQt6.QtWidgets&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;QApplication&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;QMainWindow&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;QLabel&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;QPushButton&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;QVBoxLayout&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;QWidget&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;MainWindow&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;QMainWindow&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="nf"&gt;super&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setWindowTitle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;My fancy tool&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;resize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;400&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;300&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="n"&gt;central&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;QWidget&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="n"&gt;layout&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;QVBoxLayout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;central&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;label&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;QLabel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Status: OK&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;button&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;QPushButton&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Scan&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;button&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;clicked&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="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;on_scan&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="n"&gt;layout&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addWidget&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;label&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;layout&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addWidget&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;button&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setCentralWidget&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;central&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setStyleSheet&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;
            QMainWindow { background-color: #282c34; }
            QLabel { color: #61afef; font-size: 14pt; font-weight: bold; }
            QPushButton {
                background-color: #61afef; color: #282c34;
                border-radius: 6px; padding: 8px 16px; font-weight: bold;
            }
            QPushButton:hover { background-color: #56a4e0; }
        &lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;on_scan&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;label&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setText&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Scanning...&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;QApplication&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;argv&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;window&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;MainWindow&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;show&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;exit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;exec&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A few extra lines. But the result? A dark window, a button with rounded corners and a hover effect, readable typography. &lt;strong&gt;And we didn't install a single external theme.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fz5pthwbtpit18w6qmjhj.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fz5pthwbtpit18w6qmjhj.png" alt="Tkinter vs PyQt6 — side by side: gray Windows-95 styling on the left, modern dark dashboard with KPI cards on the right" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Why PyQt6 changes the game
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Stylesheets (QSS) are basically CSS for your app
&lt;/h3&gt;

&lt;p&gt;This is probably the most underrated feature in Qt. If you know CSS, you already know 80% of QSS. Here's a slice of the dark theme I use in WatchTower:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nt"&gt;QWidget&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;background-color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#282c34&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#abb2bf&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;font-family&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;"Segoe UI"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;"Roboto"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;sans-serif&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;font-size&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;10pt&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nt"&gt;QPushButton&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;background-color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#3a3f4b&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;border&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1px&lt;/span&gt; &lt;span class="nb"&gt;solid&lt;/span&gt; &lt;span class="m"&gt;#4b5263&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;border-radius&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;4px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;padding&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;6px&lt;/span&gt; &lt;span class="m"&gt;12px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nt"&gt;QPushButton&lt;/span&gt;&lt;span class="nd"&gt;:hover&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;background-color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#4b5263&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nt"&gt;QHeaderView&lt;/span&gt;&lt;span class="nd"&gt;::section&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;background-color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#21252b&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#e6efff&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;padding&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;6px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Load it from a &lt;code&gt;.qss&lt;/code&gt; file, apply it via &lt;code&gt;app.setStyleSheet(qss)&lt;/code&gt;, and &lt;strong&gt;the whole app&lt;/strong&gt; is themed in one shot. Light, dark, cyan, accessible — you swap a string. With Tkinter you styled widget by widget, by hand, while quietly weeping.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. The signal/slot system is a superpower
&lt;/h3&gt;

&lt;p&gt;In Tkinter, components talk to each other through callbacks and sad global variables. In Qt, &lt;strong&gt;every widget can emit signals&lt;/strong&gt;, and anything else can subscribe.&lt;/p&gt;

&lt;p&gt;In WatchTower I have clickable KPI cards that emit a &lt;code&gt;card_clicked&lt;/code&gt; signal carrying the card's title (&lt;code&gt;"ACTIVE ALERTS"&lt;/code&gt;, &lt;code&gt;"MONITORED SITES"&lt;/code&gt;, etc.). The dashboard listens and filters the list accordingly:&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;PyQt6.QtCore&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;pyqtSignal&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;PyQt6.QtWidgets&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;QFrame&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;KpiCard&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;QFrame&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;card_clicked&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;pyqtSignal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# declares a signal that emits a string
&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;mousePressEvent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;card_clicked&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;emit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="nf"&gt;super&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;mousePressEvent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# On the dashboard side:
&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;alerts_card&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;card_clicked&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="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;filter_by_category&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Decoupled. Testable. Scalable. The widget doesn't know who's listening, the listener doesn't know where the signal came from. Real event-driven design — not three layers of &lt;code&gt;command=lambda: ...&lt;/code&gt; stacked on top of each other.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Threading that doesn't freeze your UI
&lt;/h3&gt;

&lt;p&gt;The worst moment in any Tkinter app is when you fire off a long-running operation and the window goes white for 30 seconds. You know it. We all know it.&lt;/p&gt;

&lt;p&gt;PyQt gives you &lt;code&gt;QThread&lt;/code&gt; and &lt;code&gt;QRunnable&lt;/code&gt; with &lt;code&gt;QThreadPool&lt;/code&gt;, and &lt;strong&gt;signals work across threads&lt;/strong&gt;. In WatchTower, my async scanner runs in a dedicated worker and pipes results back to the UI through signals:&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;PyQt6.QtCore&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;QThread&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;pyqtSignal&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ScanWorker&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;QThread&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;progress&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;pyqtSignal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;        &lt;span class="c1"&gt;# %, message
&lt;/span&gt;    &lt;span class="n"&gt;site_done&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;pyqtSignal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;           &lt;span class="c1"&gt;# structured result
&lt;/span&gt;    &lt;span class="n"&gt;finished&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;pyqtSignal&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sites&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="nf"&gt;super&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sites&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;sites&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;site&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;enumerate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sites&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
            &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;scan_one&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;site&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;   &lt;span class="c1"&gt;# blocking — that's fine in here
&lt;/span&gt;            &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;progress&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;emit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="o"&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="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sites&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt; &lt;span class="n"&gt;site&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;url&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
            &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;site_done&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;emit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;finished&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;emit&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="c1"&gt;# In the main window:
&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;worker&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;ScanWorker&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;my_sites&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;worker&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;progress&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="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;statusBar&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="n"&gt;showMessage&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;worker&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;site_done&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="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;dashboard&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;add_result&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;worker&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;start&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The UI stays responsive. You can scroll, click elsewhere, open a dialog. Tkinter doesn't do this natively, and the &lt;code&gt;threading&lt;/code&gt; + &lt;code&gt;after()&lt;/code&gt; workarounds are fragile.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. The widgets you actually want in 2026
&lt;/h3&gt;

&lt;p&gt;Tkinter gives you a &lt;code&gt;Listbox&lt;/code&gt;. Qt gives you &lt;code&gt;QListView&lt;/code&gt;, &lt;code&gt;QTableView&lt;/code&gt;, &lt;code&gt;QTreeView&lt;/code&gt; with a proper &lt;strong&gt;model/view&lt;/strong&gt; system, sorting, filtering, inline editing, drag &amp;amp; drop, and virtualization for millions of rows. Drop a &lt;code&gt;QSortFilterProxyModel&lt;/code&gt; on top and you get text search for free.&lt;/p&gt;

&lt;p&gt;For real-time charts, you have &lt;code&gt;QtCharts&lt;/code&gt; (official) or &lt;code&gt;pyqtgraph&lt;/code&gt; (extremely fast, perfect for monitoring dashboards). In WatchTower I plot live alert counts and CPU usage with zero lag.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2o0xbi4pkh1zw0p2vdsx.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2o0xbi4pkh1zw0p2vdsx.png" alt="WatchTower's multi-site grid view — four websites rendered in parallel using QWebEngineView, with live alert badges" width="605" height="340"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And icons? One line with &lt;strong&gt;qtawesome&lt;/strong&gt;:&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;qtawesome&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;qta&lt;/span&gt;
&lt;span class="n"&gt;button&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setIcon&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;qta&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;icon&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;fa5s.shield-alt&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;color&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;#61afef&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;Thousands of Font Awesome, Material Design, and Elusive icons — recolorable, resizable, vectorial.&lt;/p&gt;

&lt;h2&gt;
  
  
  WatchTower, the project that converted me
&lt;/h2&gt;

&lt;p&gt;I built WatchTower, a website defacement monitoring system, because I needed a real-time dashboard surveilling dozens of sites. Here's what PyQt6 let me do &lt;strong&gt;painlessly&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;A modern dashboard&lt;/strong&gt; with clickable KPI cards, live stats, and a multi-site grid (1, 2, or 4 sites in parallel)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Embedded WebViews&lt;/strong&gt; (&lt;code&gt;QWebEngineView&lt;/code&gt;) showing the actual rendered pages — practically impossible to do cleanly in Tkinter&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A theming system&lt;/strong&gt;: dark, light, cyan — hot-swappable without restarting&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Async workers&lt;/strong&gt; that scan in parallel without freezing the interface&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Incident timelines&lt;/strong&gt;, multi-tab config dialogs, system tray, native notifications, PDF report generation…&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Every UI brick is a reusable widget that emits its own signals. When I add a new data source, I don't rewrite half the interface — I wire up a signal and move on.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6ounnmsonkjf44tfznc8.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6ounnmsonkjf44tfznc8.png" alt="WatchTower's multi-tab configuration dialog — General, Site Signatures, Reputation, Alerts, Interface, API Keys, Backup tabs" width="605" height="340"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The same project in Tkinter? Either it wouldn't exist, or it'd be 10,000 lines of spaghetti.&lt;/p&gt;

&lt;h2&gt;
  
  
  "But Tkinter is in the standard library!"
&lt;/h2&gt;

&lt;p&gt;Yes. So is &lt;code&gt;urllib&lt;/code&gt;, and yet everyone uses &lt;code&gt;requests&lt;/code&gt;. Performance and ergonomics matter &lt;strong&gt;more&lt;/strong&gt; than skipping a &lt;code&gt;pip install&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;For reference:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pip &lt;span class="nb"&gt;install &lt;/span&gt;PyQt6 qtawesome pyqtgraph
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's all you need to ship apps that look like real tools, not freshman CS projects.&lt;/p&gt;

&lt;h2&gt;
  
  
  PyQt6 vs PyQt5?
&lt;/h2&gt;

&lt;p&gt;I started on PyQt5 and moved to PyQt6 a while back. The main differences:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Namespaced enums&lt;/strong&gt;: &lt;code&gt;Qt.AlignCenter&lt;/code&gt; becomes &lt;code&gt;Qt.AlignmentFlag.AlignCenter&lt;/code&gt;. More verbose, but much clearer.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;exec_()&lt;/code&gt; is now &lt;code&gt;exec()&lt;/code&gt;&lt;/strong&gt;: Python 3 freed the keyword.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cleaner, better typed&lt;/strong&gt;, and that's the version Qt keeps investing in.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you're starting today, go straight to PyQt6 (or PySide6 if you want the LGPL license).&lt;/p&gt;

&lt;h2&gt;
  
  
  Where to start
&lt;/h2&gt;

</description>
      <category>python</category>
      <category>pyqt</category>
      <category>gui</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Créer des interfaces Python modernes et fluides avec PyQt6</title>
      <dc:creator>h13ris</dc:creator>
      <pubDate>Mon, 04 May 2026 18:29:44 +0000</pubDate>
      <link>https://dev.to/hi3ris/creer-des-interfaces-python-modernes-et-fluides-avec-pyqt6-b5m</link>
      <guid>https://dev.to/hi3ris/creer-des-interfaces-python-modernes-et-fluides-avec-pyqt6-b5m</guid>
      <description>&lt;p&gt;J'ai un aveu à faire : pendant longtemps, quand un dev me montrait fièrement son app Python avec un bouton gris carré et une &lt;code&gt;Listbox&lt;/code&gt; qui sentait Windows 95, je hochais la tête poliment. Aujourd'hui, j'ai arrêté.&lt;/p&gt;

&lt;p&gt;Pas parce que je suis devenu méchant. Parce que &lt;strong&gt;PyQt6 existe&lt;/strong&gt;, et qu'il n'y a plus aucune excuse.&lt;/p&gt;

&lt;p&gt;Cet article, c'est ma tentative de te convaincre — toi qui ouvres encore &lt;code&gt;tkinter&lt;/code&gt; par réflexe — qu'il existe quelque chose de radicalement mieux. Je vais te montrer pourquoi, à travers des comparaisons côte à côte et des extraits réels du projet sur lequel je bosse depuis des mois : &lt;strong&gt;WatchTower&lt;/strong&gt;, un système de monitoring de défacement web construit entièrement avec PyQt6.&lt;/p&gt;

&lt;p&gt;Spoiler : à la fin, tu vas vouloir réécrire tout ce que tu as fait avec Tkinter.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4xmfrpjp213a0tkr1pqo.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4xmfrpjp213a0tkr1pqo.png" alt="Le dashboard principal de WatchTower construit avec PyQt6 — KPI cards, historique des alertes, table des sites surveillés et side panel de notifications" width="605" height="340"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Le dashboard principal de WatchTower. 100 % PyQt6, sans framework JS, sans Electron.&lt;/em&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Le drame Tkinter en trois lignes
&lt;/h2&gt;

&lt;p&gt;Soyons honnêtes deux secondes. Tkinter est livré avec Python, c'est gratuit, c'est documenté, et ça marche. Voilà. C'est le seul argument. Le reste, c'est du masochisme.&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;# Tkinter — un "dashboard" qui fait pleurer
&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;tkinter&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;tk&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;tkinter&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;ttk&lt;/span&gt;

&lt;span class="n"&gt;root&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;tk&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Tk&lt;/span&gt;&lt;span class="p"&gt;()&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;title&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Mon super outil&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&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;geometry&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;400x300&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;label&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;tk&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Label&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;root&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Statut : OK&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;bg&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;grey&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;fg&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;white&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;label&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;pack&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pady&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;button&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;tk&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Button&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;root&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Scanner&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;button&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;pack&lt;/span&gt;&lt;span class="p"&gt;()&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;mainloop&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Tu obtiens un truc gris, plat, et qui te crie « 1998 » en pleine figure. Tu veux changer la police ? Tu te bagarres avec &lt;code&gt;font=("Arial", 10)&lt;/code&gt;. Tu veux un thème sombre ? Bon courage. Tu veux un graphique en temps réel ? Tu intègres &lt;code&gt;matplotlib&lt;/code&gt; au chausse-pied et tu pries.&lt;/p&gt;

&lt;h2&gt;
  
  
  La même chose en PyQt6
&lt;/h2&gt;

&lt;p&gt;Maintenant regarde le même bidule en PyQt6 :&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;# PyQt6 — propre, moderne, et stylable
&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;sys&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;PyQt6.QtWidgets&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;QApplication&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;QMainWindow&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;QLabel&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;QPushButton&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;QVBoxLayout&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;QWidget&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;MainWindow&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;QMainWindow&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="nf"&gt;super&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setWindowTitle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Mon super outil&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;resize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;400&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;300&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="n"&gt;central&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;QWidget&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="n"&gt;layout&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;QVBoxLayout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;central&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;label&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;QLabel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Statut : OK&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;button&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;QPushButton&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Scanner&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;button&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;clicked&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="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;on_scan&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="n"&gt;layout&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addWidget&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;label&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;layout&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addWidget&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;button&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setCentralWidget&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;central&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setStyleSheet&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;
            QMainWindow { background-color: #282c34; }
            QLabel { color: #61afef; font-size: 14pt; font-weight: bold; }
            QPushButton {
                background-color: #61afef; color: #282c34;
                border-radius: 6px; padding: 8px 16px; font-weight: bold;
            }
            QPushButton:hover { background-color: #56a4e0; }
        &lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;on_scan&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;label&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setText&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Scan en cours...&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;QApplication&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;argv&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;window&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;MainWindow&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;show&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;exit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;exec&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;À peine plus de lignes. Mais le résultat ? Une fenêtre sombre, un bouton avec coins arrondis, un effet hover, et une typo lisible. &lt;strong&gt;Et tout ça sans installer un seul thème externe.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fz5pthwbtpit18w6qmjhj.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fz5pthwbtpit18w6qmjhj.png" alt="Comparaison côte à côte — à gauche Tkinter avec son look Windows 95 gris et plat, à droite un dashboard PyQt6 sombre avec KPI cards colorées et bouton arrondi" width="800" height="400"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Tkinter (à gauche) vs PyQt6 (à droite). Devine lequel donne envie de cliquer.&lt;/em&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Pourquoi PyQt6 change la donne
&lt;/h2&gt;
&lt;h3&gt;
  
  
  1. Les stylesheets (QSS), c'est du CSS pour ton app
&lt;/h3&gt;

&lt;p&gt;C'est probablement la fonctionnalité la plus sous-estimée de Qt. Tu connais CSS ? Alors tu connais déjà 80 % de QSS. Voici un extrait du thème sombre que j'utilise dans WatchTower :&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nt"&gt;QWidget&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;background-color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#282c34&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#abb2bf&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;font-family&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;"Segoe UI"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;"Roboto"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;sans-serif&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;font-size&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;10pt&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nt"&gt;QPushButton&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;background-color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#3a3f4b&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;border&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1px&lt;/span&gt; &lt;span class="nb"&gt;solid&lt;/span&gt; &lt;span class="m"&gt;#4b5263&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;border-radius&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;4px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;padding&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;6px&lt;/span&gt; &lt;span class="m"&gt;12px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nt"&gt;QPushButton&lt;/span&gt;&lt;span class="nd"&gt;:hover&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;background-color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#4b5263&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nt"&gt;QHeaderView&lt;/span&gt;&lt;span class="nd"&gt;::section&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;background-color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#21252b&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#e6efff&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;padding&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;6px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Tu charges ça depuis un fichier &lt;code&gt;.qss&lt;/code&gt;, tu l'appliques avec &lt;code&gt;app.setStyleSheet(qss)&lt;/code&gt;, et &lt;strong&gt;toute l'application&lt;/strong&gt; est habillée d'un coup. Thème clair, sombre, cyan, accessible : il suffit de switcher la string. Avec Tkinter, tu changeais ça widget par widget, à la main, en pleurant.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Le système signaux/slots, c'est un super-pouvoir
&lt;/h3&gt;

&lt;p&gt;Dans Tkinter, la communication entre composants se fait avec des callbacks et des variables globales tristes. En Qt, &lt;strong&gt;chaque widget peut émettre des signaux&lt;/strong&gt;, et n'importe qui peut s'y connecter.&lt;/p&gt;

&lt;p&gt;Dans WatchTower, j'ai des cartes KPI cliquables qui émettent un signal &lt;code&gt;card_clicked&lt;/code&gt; portant le titre de la carte (« ALERTES ACTIVES », « SITES SURVEILLÉS », etc.). Le dashboard écoute ce signal et filtre la liste en conséquence :&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;PyQt6.QtCore&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;pyqtSignal&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;PyQt6.QtWidgets&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;QFrame&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;KpiCard&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;QFrame&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;card_clicked&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;pyqtSignal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# déclare un signal qui émet une string
&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;mousePressEvent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;card_clicked&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;emit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="nf"&gt;super&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;mousePressEvent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Côté dashboard :
&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;alerts_card&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;card_clicked&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="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;filter_by_category&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;C'est découplé, testable, et ça scale. Le widget ne sait pas qui l'écoute, et le listener ne sait pas d'où vient le signal. Du &lt;strong&gt;vrai&lt;/strong&gt; event-driven, pas du &lt;code&gt;command=lambda: ...&lt;/code&gt; empilé sur trois étages.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Le threading qui ne fait pas freezer ton UI
&lt;/h3&gt;

&lt;p&gt;Le pire moment d'une app Tkinter, c'est quand tu lances une opération longue et que la fenêtre devient blanche pendant 30 secondes. Tu connais. Tout le monde connaît.&lt;/p&gt;

&lt;p&gt;PyQt te donne &lt;code&gt;QThread&lt;/code&gt; et &lt;code&gt;QRunnable&lt;/code&gt; avec &lt;code&gt;QThreadPool&lt;/code&gt;, et &lt;strong&gt;les signaux marchent à travers les threads&lt;/strong&gt;. Concrètement, dans WatchTower, mon scanner async tourne dans un worker dédié et envoie les résultats à l'UI via signaux :&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;PyQt6.QtCore&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;QThread&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;pyqtSignal&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ScanWorker&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;QThread&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;progress&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;pyqtSignal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;        &lt;span class="c1"&gt;# %, message
&lt;/span&gt;    &lt;span class="n"&gt;site_done&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;pyqtSignal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;           &lt;span class="c1"&gt;# résultat structuré
&lt;/span&gt;    &lt;span class="n"&gt;finished&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;pyqtSignal&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sites&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="nf"&gt;super&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sites&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;sites&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;site&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;enumerate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sites&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
            &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;scan_one&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;site&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;   &lt;span class="c1"&gt;# blocking, mais c'est OK ici
&lt;/span&gt;            &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;progress&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;emit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="o"&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="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sites&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt; &lt;span class="n"&gt;site&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;url&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
            &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;site_done&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;emit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;finished&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;emit&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="c1"&gt;# Dans la fenêtre principale :
&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;worker&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;ScanWorker&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;my_sites&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;worker&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;progress&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="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;statusBar&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="n"&gt;showMessage&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;worker&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;site_done&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="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;dashboard&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;add_result&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;worker&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;start&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;L'UI reste fluide. Tu peux scroller, cliquer ailleurs, ouvrir un dialog. Tkinter ne fait &lt;strong&gt;pas&lt;/strong&gt; ça nativement, et les workarounds avec &lt;code&gt;threading&lt;/code&gt; + &lt;code&gt;after()&lt;/code&gt; sont fragiles.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Les widgets qu'on attend en 2026
&lt;/h3&gt;

&lt;p&gt;Tkinter te donne une &lt;code&gt;Listbox&lt;/code&gt;. Qt te donne &lt;code&gt;QListView&lt;/code&gt;, &lt;code&gt;QTableView&lt;/code&gt;, &lt;code&gt;QTreeView&lt;/code&gt; avec un système &lt;strong&gt;modèle/vue&lt;/strong&gt; (MVC), tri, filtre, édition inline, drag &amp;amp; drop, virtualisation pour des millions de lignes. Tu peux brancher un &lt;code&gt;QSortFilterProxyModel&lt;/code&gt; au-dessus de ton modèle et obtenir une recherche textuelle gratuite.&lt;/p&gt;

&lt;p&gt;Pour les graphiques en temps réel, tu as &lt;code&gt;QtCharts&lt;/code&gt; (officiel) ou &lt;code&gt;pyqtgraph&lt;/code&gt; (très rapide, parfait pour du monitoring). Dans WatchTower, j'affiche en live l'évolution des alertes et la consommation CPU sans aucun freeze.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2o0xbi4pkh1zw0p2vdsx.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2o0xbi4pkh1zw0p2vdsx.png" alt="Vue grille multi-sites de WatchTower — quatre sites web rendus en parallèle dans des QWebEngineView avec badges d'alerte" width="605" height="340"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Quatre sites surveillés en parallèle avec leur vrai rendu HTML. Bonne chance à faire ça en Tkinter.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Et pour les icônes ? Une seule ligne avec &lt;strong&gt;qtawesome&lt;/strong&gt; :&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;qtawesome&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;qta&lt;/span&gt;
&lt;span class="n"&gt;button&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setIcon&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;qta&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;icon&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;fa5s.shield-alt&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;color&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;#61afef&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;Des milliers d'icônes Font Awesome, Material Design, Elusive — colorables, redimensionnables, vectorielles.&lt;/p&gt;

&lt;h2&gt;
  
  
  WatchTower, le projet qui m'a fait basculer
&lt;/h2&gt;

&lt;p&gt;J'ai construit WatchTower, un système de monitoring de défacement web, pour avoir un tableau de bord temps réel surveillant des dizaines de sites. Voici ce que PyQt6 m'a permis de faire &lt;strong&gt;sans douleur&lt;/strong&gt; :&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Dashboard moderne&lt;/strong&gt; avec cartes KPI cliquables, statistiques live, et grille multi-sites (1, 2 ou 4 sites en parallèle)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;WebViews intégrées&lt;/strong&gt; (&lt;code&gt;QWebEngineView&lt;/code&gt;) qui affichent le rendu réel des sites surveillés — impossible à faire proprement en Tkinter&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Système de thèmes&lt;/strong&gt; : sombre, clair, cyan, switchable à chaud sans relancer l'app&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Workers async&lt;/strong&gt; qui scannent en parallèle sans figer l'interface&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Timeline d'incidents&lt;/strong&gt;, dialogs de configuration multi-onglets, system tray, notifications natives, génération de rapports PDF…&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Chaque brique de l'UI est un widget réutilisable qui émet ses propres signaux. Quand j'ajoute une nouvelle source de données, je n'ai pas à réécrire la moitié de l'interface : je connecte un signal, et c'est plié.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6ounnmsonkjf44tfznc8.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6ounnmsonkjf44tfznc8.png" alt="Dialog de configuration multi-onglets de WatchTower — onglets Général, Sites Signatures, Réputation, Alertes, Interface, Clés API et Sauvegarde" width="605" height="340"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Sept onglets, chacun étant un widget indépendant. C'est ça, la modularité.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Le même projet en Tkinter ? Soit il n'aurait pas existé, soit il aurait fait 10 000 lignes de spaghetti.&lt;/p&gt;
&lt;h2&gt;
  
  
  « Mais Tkinter est dans la stdlib ! »
&lt;/h2&gt;

&lt;p&gt;Oui. Et &lt;code&gt;urllib&lt;/code&gt; aussi, mais tout le monde utilise &lt;code&gt;requests&lt;/code&gt;. La performance et l'ergonomie comptent &lt;strong&gt;plus&lt;/strong&gt; que l'absence d'un &lt;code&gt;pip install&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Pour info :&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pip &lt;span class="nb"&gt;install &lt;/span&gt;PyQt6 qtawesome pyqtgraph
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Et tu as de quoi faire des apps qui ressemblent à des outils pros, pas à un TP de L1.&lt;/p&gt;

&lt;h2&gt;
  
  
  Et PyQt6 vs PyQt5 ?
&lt;/h2&gt;

&lt;p&gt;J'ai démarré sur PyQt5, je suis passé à PyQt6 il y a un moment. Les différences principales :&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Enums namespacées&lt;/strong&gt; : &lt;code&gt;Qt.AlignCenter&lt;/code&gt; devient &lt;code&gt;Qt.AlignmentFlag.AlignCenter&lt;/code&gt;. Plus verbeux, mais beaucoup plus clair.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;exec_()&lt;/code&gt; devient &lt;code&gt;exec()&lt;/code&gt;&lt;/strong&gt; : Python 3 a libéré le mot-clé.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Plus propre, mieux typé&lt;/strong&gt;, et c'est la version sur laquelle Qt continue d'investir.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Si tu démarres aujourd'hui, va directement sur PyQt6 (ou PySide6 si tu veux la licence LGPL).&lt;/p&gt;

&lt;h2&gt;
  
  
  Par où commencer
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;&lt;code&gt;pip install PyQt6&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Code une fenêtre minuscule. Ressens la satisfaction d'un bouton qui a l'air normal.&lt;/li&gt;
&lt;li&gt;Découvre &lt;code&gt;QSS&lt;/code&gt;, joue avec un thème sombre.&lt;/li&gt;
&lt;li&gt;Fais ton premier signal/slot custom.&lt;/li&gt;
&lt;li&gt;Ajoute &lt;code&gt;qtawesome&lt;/code&gt; et &lt;code&gt;pyqtgraph&lt;/code&gt; quand tu veux passer au niveau au-dessus.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;La doc de Qt est dense mais excellente, et 90 % du temps, ce que tu cherches existe déjà comme widget officiel.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Faire des interfaces moches en 2026 est un &lt;strong&gt;choix&lt;/strong&gt;, pas une fatalité. PyQt6 te donne du QSS façon CSS, des signaux/slots qui tiennent la route, du threading propre, et un catalogue de widgets qui couvre la quasi-totalité des besoins.&lt;/p&gt;

&lt;p&gt;Si tu veux voir ce que ça donne à grande échelle, j'ai mis WatchTower en open-source sur &lt;a href="https://github.com/hi3ris" rel="noopener noreferrer"&gt;github.com/hi3ris&lt;/a&gt;, et le reste de mes projets traîne sur &lt;a href="https://hi3ris.blueshield.tg/" rel="noopener noreferrer"&gt;hi3ris.blueshield.tg&lt;/a&gt; si tu veux jeter un œil. Et si après ça tu reviens à Tkinter pour ta prochaine app desktop, c'est qu'on n'aura &lt;strong&gt;vraiment&lt;/strong&gt; pas eu la même conversation.&lt;/p&gt;

&lt;p&gt;Allez, à toi de jouer. Et arrête de pondre des Listbox grises.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Si cet article t'a parlé, lâche un ❤️ et un 🦄 — et dis-moi en commentaire le pire crime UI que tu aies commis avec Tkinter. Promis, pas de jugement (enfin, un peu).&lt;/em&gt;&lt;/p&gt;

</description>
      <category>python</category>
      <category>pyqt</category>
      <category>gui</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>A Website at Home? Yep, Even Without a Static IP!</title>
      <dc:creator>h13ris</dc:creator>
      <pubDate>Fri, 06 Jun 2025 23:03:13 +0000</pubDate>
      <link>https://dev.to/hi3ris/a-website-at-home-yep-even-without-a-static-ip-3c92</link>
      <guid>https://dev.to/hi3ris/a-website-at-home-yep-even-without-a-static-ip-3c92</guid>
      <description>&lt;h2&gt;
  
  
  It all started with a joke...
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;"You know, you could also host your site at home."&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;It's possible, yes. And I thought to myself: &lt;strong&gt;how could I do it... for FREE?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;To make a site accessible from the internet, what do you need?&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; A &lt;strong&gt;server&lt;/strong&gt; – in my case, it'll be my &lt;strong&gt;desktop&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt; A &lt;strong&gt;public IP&lt;/strong&gt; – I have one, but it's &lt;strong&gt;dynamic&lt;/strong&gt; (it changes).&lt;/li&gt;
&lt;li&gt; Continuous &lt;strong&gt;uptime&lt;/strong&gt; – and with the power cuts around here, we can forget about that.&lt;/li&gt;
&lt;li&gt; A &lt;strong&gt;domain name&lt;/strong&gt; – because sharing your IP address isn't very cool.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;I'll handle finding solutions for points &lt;strong&gt;1, 2, and 4&lt;/strong&gt;.&lt;br&gt;
As for point &lt;strong&gt;3&lt;/strong&gt;, I'll leave that to the &lt;strong&gt;power company&lt;/strong&gt; (good luck, folks, I'm counting on you 😉).&lt;/p&gt;

&lt;h2&gt;
  
  
  Goal: Get a free domain name that automatically points to my public IP, even when it changes.
&lt;/h2&gt;

&lt;p&gt;This is the part where I'm supposed to tell you how I discovered America and defeated Satan before stumbling upon the &lt;strong&gt;FreedomBox&lt;/strong&gt; project, blah blah blah...&lt;/p&gt;

&lt;p&gt;But let's be serious: today, I'm talking about &lt;strong&gt;GnuDIP&lt;/strong&gt;, a DDNS service that FreedomBox introduced me to – and it's the real deal.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is GnuDIP?
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;GnuDIP&lt;/strong&gt; is a &lt;strong&gt;Dynamic DNS (DDNS)&lt;/strong&gt; service.&lt;br&gt;
It allows you to link a &lt;strong&gt;domain name&lt;/strong&gt; to a &lt;strong&gt;changing IP address&lt;/strong&gt; – typically what you have if you're with a standard ISP.&lt;/p&gt;

&lt;p&gt;The public instance available at &lt;a href="https://gnudip.datasystems24.net" rel="noopener noreferrer"&gt;gnudip.datasystems24.net&lt;/a&gt; offers two free domains:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;freedombox.rocks&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;sds-ip.de&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And yes, &lt;strong&gt;it's 100% free&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Tutorial: Using GnuDIP to Get a Free Subdomain
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Step 1: Create an Account
&lt;/h3&gt;

&lt;p&gt;Go to &lt;a href="https://gnudip.datasystems24.net" rel="noopener noreferrer"&gt;https://gnudip.datasystems24.net&lt;/a&gt;&lt;br&gt;
And sign up:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fokj2ek43byh4g2fgcgvr.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fokj2ek43byh4g2fgcgvr.png" alt="GnuDIP signup form" width="475" height="350"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 2: Create a Subdomain
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Hostname&lt;/strong&gt;: choose a short, simple name with no special characters.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Domain&lt;/strong&gt;: select &lt;code&gt;sds-ip.de&lt;/code&gt; or &lt;code&gt;freedombox.rocks&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvrx8f2wd9170hnh5ymxf.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvrx8f2wd9170hnh5ymxf.png" alt="Subdomain registration" width="508" height="384"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Once registered, go back to the login page and sign in.&lt;/p&gt;

&lt;h3&gt;
  
  
  By default, the domain points to the IP you used to connect.
&lt;/h3&gt;

&lt;p&gt;But you can change it manually:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Far6vrbfe1q343sw5dhoo.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Far6vrbfe1q343sw5dhoo.png" alt="Manual IP configuration" width="618" height="525"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Even Better: Automatically Update Your Public IP
&lt;/h3&gt;

&lt;p&gt;If your IP changes regularly (as it does with many providers), GnuDIP offers a simple solution: &lt;strong&gt;the "Quick Login URL"&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Just click on &lt;strong&gt;"Set quick login URL"&lt;/strong&gt;, copy the link, and use it in a small script that runs regularly, using &lt;code&gt;cron&lt;/code&gt; for example.&lt;/p&gt;

&lt;p&gt;A simple &lt;code&gt;curl&lt;/code&gt; to this link will automatically update the domain's IP:&lt;/p&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
bash
curl "[https://gnudip.datasystems24.net/nic/update?username=...&amp;amp;password=...&amp;amp;hostname=](https://gnudip.datasystems24.net/nic/update?username=...&amp;amp;password=...&amp;amp;hostname=)..."
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

</description>
      <category>webdev</category>
      <category>opensource</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Are Quantum Computers Really Going to Break the Internet?</title>
      <dc:creator>h13ris</dc:creator>
      <pubDate>Fri, 06 Jun 2025 22:28:44 +0000</pubDate>
      <link>https://dev.to/hi3ris/so-are-quantum-computers-really-going-to-break-the-internet-2i1e</link>
      <guid>https://dev.to/hi3ris/so-are-quantum-computers-really-going-to-break-the-internet-2i1e</guid>
      <description>&lt;p&gt;It's all you hear about these days. The great "crypto-geddon" is coming. The quantum machine that will obliterate our passwords, our banks—our entire digital world.&lt;/p&gt;

&lt;p&gt;So, is it time to panic? Sell your Bitcoin, build a bunker? Or is this just another tech-apocalypse storm in a teacup?&lt;/p&gt;

&lt;p&gt;Let's be real and break it down.&lt;/p&gt;

&lt;h3&gt;
  
  
  So, What's the Actual Flaw?
&lt;/h3&gt;

&lt;p&gt;To put it simply, modern web security is like a padlock. It relies on a mathematical principle: it's super easy to multiply two huge prime numbers together, but it's a total nightmare to do the reverse. If I give you the result, it would take a normal computer thousands of years to figure out the two original numbers.&lt;/p&gt;

&lt;p&gt;The problem is, a researcher named Peter Shor figured out an algorithm back in 1994 that, on a quantum computer, could do that math in a few hours.&lt;/p&gt;

&lt;p&gt;So yes, on paper, the threat is real. Our current locks (the ones called RSA and ECC) have a known weakness. The question isn't &lt;em&gt;if&lt;/em&gt; they're vulnerable, but &lt;em&gt;when&lt;/em&gt; someone will have the key to open them.&lt;/p&gt;

&lt;h3&gt;
  
  
  Okay, But When is the Real Danger?
&lt;/h3&gt;

&lt;p&gt;Take a breath. No, your bank account isn't getting drained tomorrow. Building a quantum computer powerful enough to do this is an incredibly complex feat of engineering. We're still a long way off. The most serious estimates point to 10, 15, or even 20 years from now.&lt;/p&gt;

&lt;p&gt;So, why is everyone talking about it &lt;em&gt;now&lt;/em&gt;?&lt;/p&gt;

&lt;p&gt;Because of a much more insidious idea: &lt;strong&gt;Harvest Now, Decrypt Later.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;That's the real danger today. Imagine intelligence agencies or criminal groups vacuuming up and storing tons of encrypted data that they can't currently read. They're just waiting patiently. The day they get their hands on a quantum computer, they can unpack all of our past secrets.&lt;/p&gt;

&lt;p&gt;Your work emails, trade secrets, health records... if that information needs to stay secret for more than 10 years, the problem isn't in the future. It's right now.&lt;/p&gt;

&lt;h3&gt;
  
  
  Luckily, the Experts Haven't Been Sleeping.
&lt;/h3&gt;

&lt;p&gt;Faced with this, the crypto community has been at work for years. The solution is called &lt;strong&gt;Post-Quantum Cryptography (PQC)&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The concept? If thieves learn how to pick our current locks, let's just switch to a completely different kind of lock. Let's use weird math problems that even a quantum computer can't solve. It's as simple as that.&lt;/p&gt;

&lt;p&gt;NIST (the U.S. institute that sets the tone for tech standards) launched a global competition years ago to find the best ones. The winners have been announced, and names like Kyber and Dilithium are the new rock stars.&lt;/p&gt;

&lt;h3&gt;
  
  
  And in the Real World, Is It Actually Being Deployed?
&lt;/h3&gt;

&lt;p&gt;The good news is, this isn't just theory anymore. It's already in the wild.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Your &lt;strong&gt;Signal&lt;/strong&gt; app is already using one of these new algorithms to protect your messages, specifically against data harvesting.&lt;/li&gt;
&lt;li&gt;Your &lt;strong&gt;Chrome&lt;/strong&gt; browser is quietly testing and deploying a hybrid security model (classic + post-quantum) to keep your HTTPS connections safe.&lt;/li&gt;
&lt;li&gt;Tech giants, from Microsoft to the Linux community, are currently integrating these algorithms into the core of their systems.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The transition has definitely begun.&lt;/p&gt;

&lt;h3&gt;
  
  
  The 'But' in the Story (Because There's Always One).
&lt;/h3&gt;

&lt;p&gt;If everything is going so well, why is it taking so long? Because the task is monumental.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; &lt;strong&gt;Key Size.&lt;/strong&gt; The new keys are much larger. It makes no difference to you and me, but for billions of small IoT devices or high-speed banking systems, it's a real headache.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Inertia from the 'Old World'.&lt;/strong&gt; Crypto is everywhere. In 20-year-old legacy software, in satellites, in cars... Updating all of that without breaking anything is a titanic effort.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;The Expert Shortage.&lt;/strong&gt; There are very few people who truly master this subject from A to Z. A whole generation of developers will need to be trained on these new tools.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  So, Do We Panic or Not?
&lt;/h3&gt;

&lt;p&gt;No, there's no need to panic. Crying "crypto-geddon" is over the top.&lt;/p&gt;

&lt;p&gt;But you can't be naive, either. The threat is real, and the danger of data harvesting is immediate. The good news is that the solution is already in motion.&lt;/p&gt;

&lt;p&gt;That's where the real work is: for companies, it's time to start looking for the old locks and figuring out how to change them. For us developers, it's time to start learning and playing with these new libraries.&lt;/p&gt;

&lt;p&gt;It's less spectacular than crying wolf, for sure. But it's how we actually move forward.&lt;/p&gt;

</description>
      <category>quantum</category>
    </item>
    <item>
      <title>L'ordinateur quantique va casser Internet. Vraiment ?</title>
      <dc:creator>h13ris</dc:creator>
      <pubDate>Fri, 06 Jun 2025 22:24:09 +0000</pubDate>
      <link>https://dev.to/hi3ris/lordinateur-quantique-va-casser-internet-vraiment--2j0k</link>
      <guid>https://dev.to/hi3ris/lordinateur-quantique-va-casser-internet-vraiment--2j0k</guid>
      <description>&lt;p&gt;On n'entend que ça en ce moment. Le grand "crypto-geddon" qui arrive. La machine quantique qui va pulvériser nos mots de passe, nos banques, bref, tout notre monde numérique.&lt;/p&gt;

&lt;p&gt;Alors, on cède à la panique ? On vend ses Bitcoins, on se construit un bunker ? Ou est-ce que c'est encore une de ces tempêtes dans un verre d'eau de la tech ?&lt;/p&gt;

&lt;p&gt;Franchement, faisons le tri.&lt;/p&gt;

&lt;h3&gt;
  
  
  La faille, elle est où au juste ?
&lt;/h3&gt;

&lt;p&gt;Pour faire simple, aujourd'hui, la sécurité du web, c'est un peu comme une serrure. Elle repose sur un principe mathématique : c'est super facile de multiplier deux très grands nombres, mais c'est un cauchemar de faire l'inverse. Si je vous donne le résultat, retrouver les deux nombres de départ vous prendrait des milliers d'années avec un ordi normal.&lt;/p&gt;

&lt;p&gt;Le problème, c'est qu'un chercheur, Peter Shor, a imaginé en 1994 un algorithme qui, sur un ordinateur quantique, ferait ce calcul en quelques heures.&lt;/p&gt;

&lt;p&gt;Donc oui, sur le papier, la menace existe. Nos serrures actuelles (celles qui s'appellent RSA et ECC) ont bien une faiblesse connue. La question n'est pas de savoir &lt;em&gt;si&lt;/em&gt; elles sont vulnérables, mais &lt;em&gt;quand&lt;/em&gt; quelqu'un aura l'outil pour les ouvrir.&lt;/p&gt;

&lt;h3&gt;
  
  
  OK, mais le vrai danger, c'est pour quand ?
&lt;/h3&gt;

&lt;p&gt;Respirez. Non, votre compte en banque ne sera pas vidé demain. Un ordinateur quantique assez puissant pour ça, c'est une bête incroyablement complexe à construire. On en est encore loin. Les estimations les plus sérieuses parient sur 10, 15, voire 20 ans.&lt;/p&gt;

&lt;p&gt;Alors, pourquoi on en parle &lt;em&gt;maintenant&lt;/em&gt; ?&lt;/p&gt;

&lt;p&gt;À cause d'une idée bien plus vicieuse : &lt;strong&gt;Récolter maintenant, déchiffrer plus tard.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;C'est ça, le vrai danger aujourd'hui. Imaginez des services de renseignement ou des groupes criminels qui aspirent et stockent des tonnes de données chiffrées qu'ils ne peuvent pas lire. Ils attendent juste, patiemment. Le jour où ils auront la machine quantique, ils pourront déballer tous nos secrets passés.&lt;/p&gt;

&lt;p&gt;Vos emails pros, des secrets industriels, des données médicales... Si ces infos doivent rester secrètes dans 10 ans, le problème n'est plus du tout futuriste. Il est bien présent.&lt;/p&gt;

&lt;h3&gt;
  
  
  Heureusement, les experts n'ont pas dormi.
&lt;/h3&gt;

&lt;p&gt;Face à ça, la communauté crypto s'est mise au boulot depuis des années. La solution, c'est ce qu'on appelle la &lt;strong&gt;cryptographie post-quantique (PQC)&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Le concept ? Si les voleurs apprennent à crocheter nos serrures actuelles, changeons de type de serrure. Utilisons des problèmes mathématiques tordus que même un ordinateur quantique ne sait pas résoudre. C'est aussi simple que ça.&lt;/p&gt;

&lt;p&gt;Le NIST (l'institut américain qui fait la pluie et le beau temps sur les standards) a lancé une compétition mondiale il y a des années pour trouver les meilleures. Les gagnants ont été annoncés, et des noms comme Kyber ou Dilithium sont les nouvelles stars.&lt;/p&gt;

&lt;h3&gt;
  
  
  Et concrètement, ça se déploie ?
&lt;/h3&gt;

&lt;p&gt;La bonne nouvelle, c'est que ce n'est plus de la théorie. Ça tourne déjà.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Votre appli &lt;strong&gt;Signal&lt;/strong&gt; utilise déjà un de ces nouveaux algorithmes pour protéger vos messages, justement contre la récolte de données.&lt;/li&gt;
&lt;li&gt;Votre navigateur &lt;strong&gt;Chrome&lt;/strong&gt; teste et déploie discrètement une sécurité hybride (classique + post-quantique) pour s'assurer que vos connexions HTTPS restent sûres.&lt;/li&gt;
&lt;li&gt;Les grands de la tech, de Microsoft à la communauté Linux, sont en train de les intégrer au cœur de leurs systèmes.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;La transition a bel et bien commencé.&lt;/p&gt;

&lt;h3&gt;
  
  
  Le "mais" de l'histoire (parce qu'il y en a un).
&lt;/h3&gt;

&lt;p&gt;Si tout va bien, pourquoi ça prend du temps ? Parce que le chantier est immense.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; &lt;strong&gt;La taille des clés.&lt;/strong&gt; Les nouvelles clés sont beaucoup plus grosses. Ça ne change rien pour vous et moi, mais pour des milliards de petits objets connectés ou des systèmes bancaires ultra-rapides, c'est un vrai casse-tête.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;L'inertie du "vieux monde".&lt;/strong&gt; La crypto est partout. Dans des vieux logiciels qui tournent depuis 20 ans, dans des satellites, des voitures... Mettre à jour tout ça sans rien casser est un travail de titan.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Le manque d'experts.&lt;/strong&gt; Il y a très peu de gens qui maîtrisent vraiment le sujet de A à Z. Il va falloir former toute une génération de développeurs à ces nouveaux outils.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Alors, on panique ou pas ?
&lt;/h3&gt;

&lt;p&gt;Non, pas la peine de paniquer. Crier au "crypto-geddon" est exagéré.&lt;/p&gt;

&lt;p&gt;Mais il ne faut pas être naïf non plus. La menace est réelle, et le danger de la récolte de données est immédiat. La bonne nouvelle, c'est que la solution est en marche.&lt;/p&gt;

&lt;p&gt;Le vrai boulot, il est là : pour les entreprises, il faut commencer à regarder où sont les vieilles serrures et comment les changer. Pour nous, les développeurs, il est temps de commencer à se former et à jouer avec ces nouvelles bibliothèques.&lt;/p&gt;

&lt;p&gt;C'est moins spectaculaire que de crier au loup, c'est sûr. Mais c'est comme ça qu'on avance.&lt;/p&gt;

</description>
      <category>quantique</category>
    </item>
    <item>
      <title>Site web à la maison ? Oui, même sans IP public!</title>
      <dc:creator>h13ris</dc:creator>
      <pubDate>Thu, 15 May 2025 11:36:29 +0000</pubDate>
      <link>https://dev.to/hi3ris/site-web-a-la-maison-oui-meme-sans-ip-fixe--549j</link>
      <guid>https://dev.to/hi3ris/site-web-a-la-maison-oui-meme-sans-ip-fixe--549j</guid>
      <description>&lt;h2&gt;
  
  
  Tout est parti d'une blague...
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;"Tu peux aussi héberger ton site chez toi."&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;C'est possible, oui. Et je me suis dit : &lt;strong&gt;comment est-ce que je pourrais le faire... GRATUITEMENT ?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Pour rendre un site accessible depuis Internet, il faut quoi ?&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Un &lt;strong&gt;serveur&lt;/strong&gt; – dans mon cas, ce sera mon &lt;strong&gt;desktop&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Une &lt;strong&gt;IP publique&lt;/strong&gt; – j'en ai une, mais elle est &lt;strong&gt;dynamique&lt;/strong&gt; (elle change).&lt;/li&gt;
&lt;li&gt;Une &lt;strong&gt;disponibilité&lt;/strong&gt; continue – et là, avec les coupures d’électricité, on oublie &lt;/li&gt;
&lt;li&gt;Un &lt;strong&gt;nom de domaine&lt;/strong&gt; – parce que donner son IP, c’est pas très sexy.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Je me charge de trouver des solutions pour les points &lt;strong&gt;1, 2 et 4&lt;/strong&gt;.&lt;br&gt;&lt;br&gt;
Le point &lt;strong&gt;3&lt;/strong&gt;, je le laisse à la &lt;strong&gt;CEET&lt;/strong&gt; (courage les gars, je compte sur vous ).&lt;/p&gt;


&lt;h2&gt;
  
  
  Objectif : Avoir un nom de domaine gratuit qui s’adapte automatiquement à mon IP publique, même quand elle change.
&lt;/h2&gt;

&lt;p&gt;C’est le moment où je devrais vous raconter comment j’ai découvert l’Amérique et vaincu Satan avant de tomber sur le projet &lt;strong&gt;FreedomBox&lt;/strong&gt;, blablabla...&lt;/p&gt;

&lt;p&gt;Mais restons sérieux : aujourd’hui, je vous parle de &lt;strong&gt;GnuDIP&lt;/strong&gt;, un service DDNS que FreedomBox m’a fait découvrir – et c’est du lourd.&lt;/p&gt;


&lt;h2&gt;
  
  
  C’est quoi GnuDIP ?
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;GnuDIP&lt;/strong&gt; est un service de &lt;strong&gt;DNS dynamique (DDNS)&lt;/strong&gt;.&lt;br&gt;&lt;br&gt;
Il permet de lier un &lt;strong&gt;nom de domaine&lt;/strong&gt; à une &lt;strong&gt;adresse IP changeante&lt;/strong&gt; – typiquement ce que tu as si tu es chez un FAI standard.&lt;/p&gt;

&lt;p&gt;L’instance publique dispo sur &lt;a href="https://gnudip.datasystems24.net" rel="noopener noreferrer"&gt;gnudip.datasystems24.net&lt;/a&gt; propose deux domaines gratuits :&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;freedombox.rocks&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;sds-ip.de&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Et oui, &lt;strong&gt;c’est 100 % gratuit&lt;/strong&gt;.&lt;/p&gt;


&lt;h2&gt;
  
  
  Tutoriel : Utiliser GnuDIP pour obtenir un sous-domaine gratuit
&lt;/h2&gt;
&lt;h3&gt;
  
  
  Étape 1 : Création d’un compte
&lt;/h3&gt;

&lt;p&gt;Rendez-vous sur &lt;a href="https://gnudip.datasystems24.net" rel="noopener noreferrer"&gt;https://gnudip.datasystems24.net&lt;/a&gt;&lt;br&gt;&lt;br&gt;
Et inscrivez-vous :&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fokj2ek43byh4g2fgcgvr.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fokj2ek43byh4g2fgcgvr.png" alt="inscription" width="475" height="350"&gt;&lt;/a&gt;&lt;/p&gt;


&lt;h3&gt;
  
  
  Étape 2 : Créer un sous-domaine
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Hostname&lt;/strong&gt; : choisissez un nom court, simple, sans caractères spéciaux.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Domain&lt;/strong&gt; : sélectionnez &lt;code&gt;sds-ip.de&lt;/code&gt; ou &lt;code&gt;freedombox.rocks&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvrx8f2wd9170hnh5ymxf.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvrx8f2wd9170hnh5ymxf.png" alt="enregistrement" width="508" height="384"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Une fois enregistré, retournez sur la page de connexion et connectez-vous.&lt;/p&gt;


&lt;h3&gt;
  
  
  Par défaut, le domaine pointe vers l’IP que vous avez utilisée pour vous connecter.
&lt;/h3&gt;

&lt;p&gt;Mais vous pouvez la modifier manuellement :&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Far6vrbfe1q343sw5dhoo.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Far6vrbfe1q343sw5dhoo.png" alt="configs" width="618" height="525"&gt;&lt;/a&gt;&lt;/p&gt;


&lt;h3&gt;
  
  
  Mieux encore : Mettez à jour automatiquement votre IP publique
&lt;/h3&gt;

&lt;p&gt;Si votre IP change régulièrement (comme chez beaucoup de fournisseurs), GnuDIP vous propose une solution simple : &lt;strong&gt;le "Quick Login URL"&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Il suffit de cliquer sur &lt;strong&gt;"Set quick login URL"&lt;/strong&gt;, copier le lien, et l’utiliser dans un petit script qui s’exécute régulièrement via &lt;code&gt;cron&lt;/code&gt; par exemple.&lt;/p&gt;

&lt;p&gt;Un simple &lt;code&gt;curl&lt;/code&gt; vers ce lien mettra automatiquement à jour l’IP du domaine :&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="s2"&gt;"https://gnudip.datasystems24.net/nic/update?username=...&amp;amp;password=...&amp;amp;hostname=..."&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvw26sjknezcg6its9uzt.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvw26sjknezcg6its9uzt.png" alt="Dynamique changes" width="800" height="355"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h3&gt;
  
  
  Et tada !!!
&lt;/h3&gt;

&lt;p&gt;Votre domaine gratuit pointe vers votre réseau local, même si votre IP change.&lt;/p&gt;

&lt;p&gt;Il ne vous reste plus qu’à faire une redirection de port sur votre &lt;strong&gt;routeur&lt;/strong&gt; pour exposer un service (web, SSH, etc.).&lt;/p&gt;




&lt;h2&gt;
  
  
  Bonus1 : Et DuckDNS alors ?
&lt;/h2&gt;

&lt;p&gt;Si vous préférez une alternative encore plus simple, il y a aussi &lt;a href="https://www.duckdns.org" rel="noopener noreferrer"&gt;DuckDNS.org&lt;/a&gt;, qui offre des sous-domaines en &lt;code&gt;duckdns.org&lt;/code&gt; et fonctionne aussi avec un script ou une URL à appeler périodiquement.&lt;/p&gt;

&lt;p&gt;Configuration de base (avec &lt;code&gt;cron&lt;/code&gt;) :&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"url=https://www.duckdns.org/update?domains=MONDOMAINE&amp;amp;token=MONTOKEN&amp;amp;ip="&lt;/span&gt; | curl &lt;span class="nt"&gt;-k&lt;/span&gt; &lt;span class="nt"&gt;-o&lt;/span&gt; ~/duckdns/duck.log &lt;span class="nt"&gt;-K&lt;/span&gt; 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Et ajoutez ce script à votre crontab :&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="k"&gt;*&lt;/span&gt;/5 &lt;span class="k"&gt;*&lt;/span&gt; &lt;span class="k"&gt;*&lt;/span&gt; &lt;span class="k"&gt;*&lt;/span&gt; &lt;span class="k"&gt;*&lt;/span&gt; ~/duckdns/update.sh &lt;span class="o"&gt;&amp;gt;&lt;/span&gt;/dev/null 2&amp;gt;&amp;amp;1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Bonus : Se connecter à son serveur chez soi en SSH grâce au domaine dynamique
&lt;/h2&gt;

&lt;p&gt;Maintenant que votre domaine pointe toujours vers votre IP publique, vous pouvez même l'utiliser pour accéder à votre machine à distance via &lt;strong&gt;SSH&lt;/strong&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Exemple : connexion SSH avec domaine GnuDIP
&lt;/h3&gt;

&lt;p&gt;Imaginons que vous avez configuré le domaine &lt;code&gt;monserveur.sds-ip.de&lt;/code&gt; pour pointer vers votre IP publique.&lt;br&gt;&lt;br&gt;
Sur votre machine distante (là où vous voulez te connecter), il vous suffit d'utiliser :&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ssh utilisateur@monserveur.sds-ip.de
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Et voilà, vous êtes chez vous&lt;/p&gt;

&lt;h3&gt;
  
  
  Pré-requis côté maison :
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Votre &lt;strong&gt;box/routeur&lt;/strong&gt; doit rediriger le port &lt;strong&gt;22&lt;/strong&gt; (ou un port personnalisé si vous avez modifié &lt;code&gt;sshd&lt;/code&gt;) vers votre machine.&lt;/li&gt;
&lt;li&gt;Votre machine doit être &lt;strong&gt;allumée&lt;/strong&gt;  et le service &lt;strong&gt;SSH actif&lt;/strong&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;Astuce sécurité : changez le port SSH par défaut, utilisez une &lt;strong&gt;clé SSH&lt;/strong&gt; au lieu du mot de passe, et configurez &lt;strong&gt;fail2ban&lt;/strong&gt; pour limiter les attaques.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;p&gt;Cette configuration, combinée au domaine dynamique, vous permet un &lt;strong&gt;accès sécurisé et stable&lt;/strong&gt; à votre environnement local, même si votre IP change toutes les 12 heures !&lt;/p&gt;




&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Finalement, j’ai quand même pris un &lt;strong&gt;VPS&lt;/strong&gt; pour héberger mon &lt;a href="https://hi3ris.blueshield.tg/" rel="noopener noreferrer"&gt;portfolio&lt;/a&gt;.&lt;br&gt;&lt;br&gt;
Mais, juste pour le fun, il est aussi dispo via GnuDIP ici :&lt;br&gt;&lt;br&gt;
&lt;a href="https://hi3ris.sds-ip.de" rel="noopener noreferrer"&gt;https://hi3ris.sds-ip.de&lt;/a&gt;&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>tutorial</category>
      <category>opensource</category>
    </item>
  </channel>
</rss>
