Skip to content

Instantly share code, notes, and snippets.

@senko
Created May 23, 2025 14:42
Show Gist options
  • Save senko/d58c7aabdeb4b00fd9a0a88cff199bd1 to your computer and use it in GitHub Desktop.
Save senko/d58c7aabdeb4b00fd9a0a88cff199bd1 to your computer and use it in GitHub Desktop.
QR Code generator app for GTK/GNOME
#!/usr/bin/env -S uv run -s
# This script generates a QR code from a string input and displays it in a GTK window.
# /// script
# requires-python = ">=3.13"
# dependencies = [
# "pillow>=11.2.1",
# "pygobject==3.50.0",
# "qrcode>=8.2",
# ]
# ///
# Run from command line with: `python3 qrgen.py <data>` or just `python3 qrgen.py`
# If no data is provided, a GUI window will open for input.
# To use directly from GNOME Shell or equivalent start menus, you can
# create a .desktop file with the following content:
#
# ```ini
# [Desktop Entry]
# Name=QR Gen
# Exec=/usr/local/bin/qrgen
# Type=Application
# Keywords=qr;qrgen
# Icon=/home/<youruser>/.local/share/icons/qrgen.png
# ```
# Make sure to replace `/usr/local/bin/qrgen` with the actual path to
# your script, and point the icon to (any) PNG file.
import gi
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk, GdkPixbuf, Gdk
import qrcode
from PIL import Image as PILImage # Rename to avoid conflict with Gtk.Image
import io
import sys
def generate_qr_pil_image(data_string, box_size=10, border=4):
"""
Generates a QR code and returns it as a PIL Image object.
"""
qr = qrcode.QRCode(
version=None, # Let the library choose the version based on data
error_correction=qrcode.constants.ERROR_CORRECT_L,
box_size=box_size,
border=border,
)
qr.add_data(data_string)
qr.make(fit=True)
# Create an image from the QR Code instance
# Ensure it's RGB for easier conversion to GdkPixbuf
img = qr.make_image(fill_color="black", back_color="white").convert('RGB')
return img
def display_qr_in_gtk(pil_image, title_text="QR Code", existing_window=None):
"""
Displays a PIL Image in a GTK3 window.
If existing_window is provided, it clears and reuses that window.
"""
# Convert PIL Image to GdkPixbuf
# Method 1: Using io.BytesIO (more robust for various PIL formats)
byte_io = io.BytesIO()
pil_image.save(byte_io, format='PNG') # Save as PNG to the in-memory buffer
byte_io.seek(0) # Rewind the buffer to the beginning
# Create a GdkPixbufLoader for PNG
loader = GdkPixbuf.PixbufLoader.new_with_type('png')
try:
loader.write(byte_io.read())
except gi.repository.GLib.Error as e:
print(f"Error loading Pixbuf: {e}")
return
finally:
loader.close()
pixbuf = loader.get_pixbuf()
if not pixbuf:
print("Failed to create GdkPixbuf.")
return
# Handler for keyboard shortcuts
def on_key_press(widget, event):
# Check for ESC key
if event.keyval == Gdk.KEY_Escape:
Gtk.main_quit()
return True
# Check for Ctrl+W and Ctrl+Q
if (event.state & Gdk.ModifierType.CONTROL_MASK) != 0:
if event.keyval == Gdk.KEY_q or event.keyval == Gdk.KEY_w:
Gtk.main_quit()
return True
return False
if existing_window:
# Clear the existing window content
for child in existing_window.get_children():
existing_window.remove(child)
window = existing_window
window.set_title(title_text)
else:
# Create a new GTK window
window = Gtk.Window(title=title_text)
window.set_position(Gtk.WindowPosition.CENTER)
window.connect("destroy", Gtk.main_quit)
window.connect("key-press-event", on_key_press)
window.set_border_width(10)
# Create a GTK Image widget
gtk_image = Gtk.Image()
gtk_image.set_from_pixbuf(pixbuf)
# Add the image to the window
window.add(gtk_image)
window.show_all()
if not existing_window:
Gtk.main()
return window
def create_input_window():
"""
Creates a GTK window with a text input field.
When the user enters text and presses Enter, it generates and displays the QR code.
"""
window = Gtk.Window(title="Enter text for QR code")
window.set_position(Gtk.WindowPosition.CENTER)
window.connect("destroy", Gtk.main_quit)
window.set_border_width(10)
window.set_default_size(400, -1) # Set width, let height adjust automatically
# Handler for keyboard shortcuts
def on_key_press(widget, event):
# Check for ESC key
if event.keyval == Gdk.KEY_Escape:
Gtk.main_quit()
return True
# Check for Ctrl+W and Ctrl+Q
if (event.state & Gdk.ModifierType.CONTROL_MASK) != 0:
if event.keyval == Gdk.KEY_q or event.keyval == Gdk.KEY_w:
Gtk.main_quit()
return True
return False
window.connect("key-press-event", on_key_press)
# Create a vertical box to hold our widgets
vbox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=8)
window.add(vbox)
# Add a label
label = Gtk.Label(label="Enter text or URL to encode as QR code:")
vbox.pack_start(label, False, False, 0)
# Add a text entry
entry = Gtk.Entry()
entry.set_placeholder_text("Type or paste here and press Enter")
vbox.pack_start(entry, False, False, 0)
# Function to generate QR code from entry text
def generate_qr_from_entry(widget):
data_to_encode = entry.get_text().strip()
if not data_to_encode:
return
print(f"Generating QR code for: \"{data_to_encode}\"")
try:
pil_qr_image = generate_qr_pil_image(data_to_encode)
except Exception as e:
# Show error in the window
for child in vbox.get_children():
vbox.remove(child)
error_label = Gtk.Label(label=f"Error generating QR code: {e}")
vbox.pack_start(error_label, True, True, 0)
vbox.show_all()
return
window_title = f"QR Code: {data_to_encode[:50]}"
if len(data_to_encode) > 50:
window_title += "..."
# Display QR code in the same window
display_qr_in_gtk(pil_qr_image, window_title, window)
# Connect the entry's activate signal (Enter key press)
entry.connect("activate", generate_qr_from_entry)
window.show_all()
# Focus the entry automatically
entry.grab_focus()
Gtk.main()
def main():
if len(sys.argv) < 2:
# Show input window if no arguments provided
create_input_window()
else:
# Join all arguments after the script name to form the data string
data_to_encode = " ".join(sys.argv[1:])
print(f"Generating QR code for: \"{data_to_encode}\"")
try:
pil_qr_image = generate_qr_pil_image(data_to_encode)
except Exception as e:
print(f"Error generating QR code: {e}")
sys.exit(1)
window_title = f"QR Code: {data_to_encode[:50]}"
if len(data_to_encode) > 50:
window_title += "..."
display_qr_in_gtk(pil_qr_image, window_title)
if __name__ == "__main__":
main()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment