checkpoint
This commit is contained in:
@ -43,7 +43,7 @@ class Kiosk(tk.Tk):
|
||||
|
||||
def add_home_button(self, frame):
|
||||
# Create the "Start Over" button
|
||||
home_button = tk.Button(frame, text="Start Over from the beginning", command=self.show_warning_dialog, bg='peach puff', width=24, font=GlobalVars.BUTTON_FONT)
|
||||
home_button = tk.Button(frame, text="Start over from the beginning", command=self.show_warning_dialog, bg='peach puff', width=24, font=GlobalVars.BUTTON_FONT)
|
||||
home_button.place(x=0, y=0) # top-left corner
|
||||
|
||||
def show_warning_dialog(self):
|
||||
|
||||
179
kiosk/screens/add_user.py
Normal file
179
kiosk/screens/add_user.py
Normal file
@ -0,0 +1,179 @@
|
||||
"""Add User screen - scan QR code of SSB public key."""
|
||||
import tkinter as tk
|
||||
from PIL import Image, ImageTk
|
||||
import cv2
|
||||
import json
|
||||
import re
|
||||
import time
|
||||
|
||||
from pyzbar.pyzbar import decode as pyzbar_decode
|
||||
|
||||
from kiosk.config import CONFIG, BG_COLOR
|
||||
from kiosk.state import GlobalVars
|
||||
from kiosk.utils import get_preferred_camera_index
|
||||
from kiosk.widgets import RoundedLabel
|
||||
|
||||
SSB_KEY_REGEX = re.compile(r'^@[A-Za-z0-9+/]{43}=\.ed25519$')
|
||||
|
||||
|
||||
class ScreenAddUser(tk.Frame):
|
||||
"""Scan a QR code containing an SSB public key to add a new user."""
|
||||
def __init__(self, master):
|
||||
tk.Frame.__init__(self, master, bg=BG_COLOR)
|
||||
master.add_home_button(self)
|
||||
|
||||
self.scanned_key = None
|
||||
|
||||
# Main container
|
||||
self.main_container = tk.Frame(self, bg=BG_COLOR)
|
||||
self.main_container.pack(fill='both', expand=True, pady=(60, 0))
|
||||
|
||||
# Left side: instructions
|
||||
self.instruction_frame = tk.Frame(self.main_container, bg=BG_COLOR)
|
||||
self.instruction_frame.pack(side='left', fill='both', expand=True, padx=20, pady=20)
|
||||
|
||||
self.instruction_label = RoundedLabel(
|
||||
self.instruction_frame,
|
||||
text="You can input your SSB public key into the kiosk via QR code. First, copy your public key from your Scuttlebutt client (in Manyverse you'll want to tap your key and then tap 'Copy cypherlink'.\n\nNext, navigate to www.cust.ooo/ssb and paste your key into the text box on that page and click Generate QR. This will generate a QR code for you. Hold this code in front of the kiosk to scan the code and load your public key into this kiosk.",
|
||||
font=("Georgia", 16),
|
||||
bg='white',
|
||||
wraplength=400,
|
||||
justify='left'
|
||||
)
|
||||
self.instruction_label.pack(expand=True)
|
||||
|
||||
# Right side container
|
||||
self.right_frame = tk.Frame(self.main_container, bg=BG_COLOR)
|
||||
self.right_frame.pack(side='right', fill='both', expand=True, padx=20, pady=20)
|
||||
|
||||
# Video feed
|
||||
self.video = tk.Label(self.right_frame, bg='black')
|
||||
self.video.pack(pady=(0, 10))
|
||||
|
||||
# Status/detected key text
|
||||
self.status_label = tk.Label(
|
||||
self.right_frame,
|
||||
text="Waiting for QR code...",
|
||||
font=("Georgia", 14),
|
||||
bg=BG_COLOR, fg='black',
|
||||
wraplength=500,
|
||||
justify='center'
|
||||
)
|
||||
self.status_label.pack(pady=(0, 10))
|
||||
|
||||
# Button row
|
||||
self.button_frame = tk.Frame(self.right_frame, bg=BG_COLOR)
|
||||
self.button_frame.pack(pady=10)
|
||||
|
||||
# Cancel button
|
||||
self.cancel_button = tk.Button(
|
||||
self.button_frame,
|
||||
text="Cancel",
|
||||
command=self._go_back,
|
||||
height=2, width=15,
|
||||
bg='peach puff',
|
||||
font=GlobalVars.BUTTON_FONT
|
||||
)
|
||||
self.cancel_button.pack(side='left', padx=10)
|
||||
|
||||
# Done button (hidden until valid key scanned)
|
||||
self.done_button = tk.Button(
|
||||
self.button_frame,
|
||||
text="Yes!",
|
||||
command=self._done,
|
||||
height=2, width=15,
|
||||
bg='peach puff',
|
||||
font=GlobalVars.BUTTON_FONT
|
||||
)
|
||||
# Don't pack yet - shown after valid scan
|
||||
|
||||
# Start camera
|
||||
self.cap = cv2.VideoCapture(get_preferred_camera_index(CONFIG["camera"]["preferred_name"]))
|
||||
self.cap.set(cv2.CAP_PROP_FRAME_WIDTH, 640)
|
||||
self.cap.set(cv2.CAP_PROP_FRAME_HEIGHT, 480)
|
||||
|
||||
self.frame_count = 0
|
||||
self.TIMEOUT_SECONDS = 120
|
||||
self.start_time = time.time()
|
||||
self.update_frame()
|
||||
|
||||
def update_frame(self):
|
||||
# Check for idle timeout
|
||||
if time.time() - self.start_time >= self.TIMEOUT_SECONDS:
|
||||
self.cap.release()
|
||||
from kiosk.screens.home import Screen0
|
||||
self.master.switch_frame(Screen0)
|
||||
return
|
||||
|
||||
ret, frame = self.cap.read()
|
||||
self.frame_count += 1
|
||||
|
||||
if ret:
|
||||
# Process every 3rd frame for QR detection
|
||||
if self.frame_count % 3 == 0 and self.scanned_key is None:
|
||||
gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
|
||||
results = pyzbar_decode(gray)
|
||||
|
||||
if results:
|
||||
qr_value = results[0].data.decode('utf-8')
|
||||
if qr_value:
|
||||
self.start_time = time.time() # Reset idle timer
|
||||
if SSB_KEY_REGEX.match(qr_value):
|
||||
self.scanned_key = qr_value
|
||||
self.status_label.configure(
|
||||
text=f"Is this your key?\n{qr_value}",
|
||||
fg='dark green'
|
||||
)
|
||||
self.done_button.pack(side='left', padx=10)
|
||||
else:
|
||||
self.status_label.configure(
|
||||
text="Sorry, we need a QR code of a valid Scuttlebutt public key",
|
||||
fg='red'
|
||||
)
|
||||
|
||||
# Display frame
|
||||
rgb_frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
|
||||
image = Image.fromarray(rgb_frame)
|
||||
imgtk = ImageTk.PhotoImage(image=image)
|
||||
self.video.imgtk = imgtk
|
||||
self.video.configure(image=imgtk)
|
||||
|
||||
self.after(10, self.update_frame)
|
||||
|
||||
def _go_back(self):
|
||||
self.cap.release()
|
||||
from kiosk.screens.ssb_selection import Screen2
|
||||
self.master.switch_frame(Screen2)
|
||||
|
||||
def _done(self):
|
||||
if self.scanned_key is None:
|
||||
return
|
||||
|
||||
GlobalVars.selected_user = self.scanned_key
|
||||
self._save_user(self.scanned_key)
|
||||
self.cap.release()
|
||||
|
||||
from kiosk.screens.camera import Screen3
|
||||
self.master.switch_frame(Screen3)
|
||||
|
||||
def _save_user(self, key):
|
||||
"""Append user to users.json if not already present."""
|
||||
try:
|
||||
with open('users.json') as f:
|
||||
users = json.load(f)
|
||||
except (FileNotFoundError, json.JSONDecodeError):
|
||||
users = []
|
||||
|
||||
# Check if already exists
|
||||
if any(u['id'] == key for u in users):
|
||||
return
|
||||
|
||||
users.append({"id": key, "alias": ""})
|
||||
|
||||
with open('users.json', 'w') as f:
|
||||
json.dump(users, f)
|
||||
|
||||
def destroy(self):
|
||||
if self.cap.isOpened():
|
||||
self.cap.release()
|
||||
super().destroy()
|
||||
@ -21,7 +21,7 @@ class Screen3(tk.Frame):
|
||||
tk.Frame.__init__(self, master, bg=BG_COLOR)
|
||||
|
||||
# Create the "Start Over" button
|
||||
home_button = tk.Button(text="Start Over", command=self.show_warning_dialog, bg='peach puff', font=GlobalVars.BUTTON_FONT)
|
||||
home_button = tk.Button(text="Start over from the beginning", command=self.show_warning_dialog, bg='peach puff', font=GlobalVars.BUTTON_FONT)
|
||||
home_button.place(x=0, y=0) # top-left corner
|
||||
|
||||
# Prefer the external NexiGo USB webcam when available
|
||||
|
||||
@ -199,8 +199,8 @@ class Screen8(tk.Frame, DrawingMixin):
|
||||
col1_frame.pack(side='left', padx=(15, 25), anchor='n', pady=20)
|
||||
|
||||
# Start Over button (replaces home button) - Screen8
|
||||
tk.Button(col1_frame, text="Start Over", command=master.show_warning_dialog,
|
||||
height=2, width=14, bg='peach puff', font=GlobalVars.BUTTON_FONT).pack(pady=(0, 20))
|
||||
tk.Button(col1_frame, text="Start over from the beginning", command=master.show_warning_dialog,
|
||||
height=2, width=24, bg='peach puff', font=GlobalVars.BUTTON_FONT).pack(pady=(0, 20))
|
||||
|
||||
# Instruction label
|
||||
self.label = RoundedLabel(col1_frame, text="Draw your\nribbon :)",
|
||||
|
||||
@ -177,8 +177,8 @@ class NoQRDrawingScreen(tk.Frame, DrawingMixin):
|
||||
col1_frame.pack(side='left', padx=(15, 25), anchor='n', pady=20)
|
||||
|
||||
# Start Over button - NoQRDrawingScreen
|
||||
tk.Button(col1_frame, text="Start Over", command=master.show_warning_dialog,
|
||||
height=2, width=14, bg='peach puff', font=GlobalVars.BUTTON_FONT).pack(pady=(0, 20))
|
||||
tk.Button(col1_frame, text="Start over from the beginning", command=master.show_warning_dialog,
|
||||
height=2, width=24, bg='peach puff', font=GlobalVars.BUTTON_FONT).pack(pady=(0, 20))
|
||||
|
||||
# Instruction label
|
||||
self.label = RoundedLabel(col1_frame, text="Draw your\nribbon :)",
|
||||
|
||||
@ -93,8 +93,8 @@ class ParticipationStickerDrawingScreen(tk.Frame, DrawingMixin):
|
||||
col1_frame = tk.Frame(main_container, bg=BG_COLOR)
|
||||
col1_frame.pack(side='left', padx=(15, 25), anchor='n', pady=20)
|
||||
|
||||
tk.Button(col1_frame, text="Start Over", command=master.show_warning_dialog,
|
||||
height=2, width=14, bg='peach puff', font=GlobalVars.BUTTON_FONT).pack(pady=(0, 20))
|
||||
tk.Button(col1_frame, text="Start over from the beginning", command=master.show_warning_dialog,
|
||||
height=2, width=24, bg='peach puff', font=GlobalVars.BUTTON_FONT).pack(pady=(0, 20))
|
||||
|
||||
self.label = RoundedLabel(col1_frame, text="Draw your\nsticker :)",
|
||||
wraplength=150, font=GlobalVars.TEXT_FONT, bg='white')
|
||||
@ -181,8 +181,8 @@ class ParticipationRibbonDrawingScreen(tk.Frame, DrawingMixin):
|
||||
col1_frame = tk.Frame(main_container, bg=BG_COLOR)
|
||||
col1_frame.pack(side='left', padx=(5, 10), anchor='n', pady=20)
|
||||
|
||||
tk.Button(col1_frame, text="Start Over", command=master.show_warning_dialog,
|
||||
height=2, width=14, bg='peach puff', font=GlobalVars.BUTTON_FONT).pack(pady=(0, 20))
|
||||
tk.Button(col1_frame, text="Start over from the beginning", command=master.show_warning_dialog,
|
||||
height=2, width=24, bg='peach puff', font=GlobalVars.BUTTON_FONT).pack(pady=(0, 20))
|
||||
|
||||
self.label = RoundedLabel(col1_frame, text="Draw your\nribbon :)",
|
||||
wraplength=150, font=GlobalVars.TEXT_FONT, bg='white')
|
||||
|
||||
@ -26,7 +26,7 @@ class Screen13(tk.Frame):
|
||||
container.place(relx=0.5, rely=0.5, anchor='center')
|
||||
|
||||
# instructions
|
||||
RoundedLabel(container, text="Wonderful! It is now time to post your item to Scuttlebutt and to print your tag. You can still cancel by hitting Start Over if you like.", wraplength=600, font=GlobalVars.TEXT_FONT, bg='white').grid(row=0, column=0, columnspan=2)
|
||||
RoundedLabel(container, text="Wonderful! It is now time to post your item to Scuttlebutt and to print your tag. You can still cancel by hitting Start over from the beginning if you like.", wraplength=600, font=GlobalVars.TEXT_FONT, bg='white').grid(row=0, column=0, columnspan=2)
|
||||
|
||||
# buttons
|
||||
master.add_home_button(self)
|
||||
|
||||
@ -118,6 +118,15 @@ class Screen2(tk.Frame):
|
||||
GlobalVars.selected_user = None
|
||||
go_to_screen3()
|
||||
|
||||
# The 'Add User' button to scan a QR code
|
||||
def go_to_add_user():
|
||||
from kiosk.screens.add_user import ScreenAddUser
|
||||
master.switch_frame(ScreenAddUser)
|
||||
|
||||
self.add_user_button = tk.Button(self.button_frame, text="Add User", command=go_to_add_user,
|
||||
height=2, width=20, bg='peach puff', font=GlobalVars.BUTTON_FONT)
|
||||
self.add_user_button.pack(side="left", padx=(10, 10))
|
||||
|
||||
# The 'Cancel' button to skip without selecting a user
|
||||
self.cancel_button = tk.Button(self.button_frame, text="Cancel", command=cancel_selection,
|
||||
height=2, width=20, bg='peach puff', font=GlobalVars.BUTTON_FONT)
|
||||
|
||||
84
ssb-qr.html
Normal file
84
ssb-qr.html
Normal file
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user