Created
May 23, 2025 14:42
-
-
Save senko/d58c7aabdeb4b00fd9a0a88cff199bd1 to your computer and use it in GitHub Desktop.
QR Code generator app for GTK/GNOME
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/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