From 0f9ad0d0b810def89705d4c7fbbff962b16ba4c4 Mon Sep 17 00:00:00 2001 From: "B. Stack" Date: Tue, 24 Sep 2024 19:57:24 -0400 Subject: dpkg build recipe and assets --- fprintd_tk | 262 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 262 insertions(+) create mode 100755 fprintd_tk (limited to 'fprintd_tk') diff --git a/fprintd_tk b/fprintd_tk new file mode 100755 index 0000000..0f4f947 --- /dev/null +++ b/fprintd_tk @@ -0,0 +1,262 @@ +#!/usr/bin/env python3 +# File: fprintd_tk.py +# Location: https://bgstack15.cgit/fprintd-tk +# Author: bgstack15 +# Startdate: 2024-09-22-1 14:26 +# SPDX-License-Identifier: GPL-3.0-only +# Title: Gui for fprintd +# Purpose: tkinter desktop gui app for management fingerprint enrollments +# Project: fprintd-tk +# History: +# Usage: +# Run from window manager application menu +# Reference: +# stackrpms_tk.py +# Improve: +# Dependencies: +# dep-devuan: python3:any, python3-tkstackrpms, python3-tk, python3-pil +# fprintd_tk_lib.py +# Documentation: +# README.md + +import tkinter as tk, os, tkinter.simpledialog, sys, threading, time +import tkstackrpms as stk +import fprintd_tk_lib as lib +from PIL import Image, ImageTk + +ABOUT_TEXT = """ +fprintd_tk \"Gui for fprintd\" +(C) 2024 bgstack15 +SPDX-License-Identifier: GPL-3.0-only +Icons adapted from Numix-Icon-Theme-Circle (GPL 3) +""" + +# These will be used a lot in the program. +str_hands = ["left","right"] +str_fingers = ["thumb","index-finger","middle-finger","ring-finger","little-finger"] + +class App(tk.Frame): + def __init__(self, master): + super().__init__(master) + # variables + self.statustext = tk.StringVar() + self.advanced = tk.BooleanVar(value=False) + self.advanced.trace_add("write",self.load_data_into_form) + self.current_username = os.getenv("USER") + self.username = tk.StringVar(value=self.current_username) + self.verbose = tk.BooleanVar(value=False) + + # configurable by admin or installation + img_path = os.path.join(os.path.dirname(os.path.realpath(__file__)),"images") + + self.master.title("Gui for fprintd") + imgicon = stk.get_scaled_icon("fingerprint-gui",24,"default", "","apps") + self.master.tk.call("wm","iconphoto",self.master._w,imgicon) + menu = tk.Menu(self.master) + menu_file = tk.Menu(menu,tearoff=0) + menu_file.add_checkbutton(label="Advanced features", variable=self.advanced, underline=0, state="disabled") + menu_file.add_checkbutton(label="Verbose", variable=self.verbose, underline=0) + menu_file.add_command(label="Delete...", command=self.func_delete, underline=0) + menu_file.add_separator() + menu_file.add_command(label="Exit", command=self.func_exit, underline=1) + menu.add_cascade(label="File",menu=menu_file,underline=0) + menu_help = tk.Menu(menu,tearoff=0) + menu_help.add_command(label="About", command=self.func_about, underline=0) + menu.add_cascade(label="Help",menu=menu_help,underline=0) + self.master.config(menu=menu) + self.grid() # use this instead of pack() + self.background_color = self.master.cget("bg") + + # prepare finger images + try: + img_path + "" + except: + img_path = os.path.join(os.path.dirname(os.path.realpath(__file__)),"images") + self.img_notenrolled = ImageTk.PhotoImage(stk.image_from_svg(os.path.join(img_path,"fingerprint-gui.svg"),32)) + self.img_enrolled = ImageTk.PhotoImage(stk.image_from_svg(os.path.join(img_path,"fingerprint-enrolled.svg"),32)) + + # advanced pane, top + self.frm_advanced = tk.Frame(self.master) + # start hidden + #self.frm_advanced.grid(row=0,column=0,columnspan=100) + tk.Label(self.frm_advanced,text="User").grid(row=0,column=0) + self.ent_username = stk.Entry(self.frm_advanced,textvariable=self.username,func=self.load_data_into_form) + self.ent_username.grid(row=0,column=1) + + # action pane, left side + self.frm_actions = tk.Frame(self.master) + self.frm_actions.grid(row=1,column=0) + self.action = tk.IntVar(value=1) + self.last_fingerbutton_used = tk.StringVar() + stk.Radiobutton(self.frm_actions,value=1,text="enroll",variable=self.action,underline=0).grid(row=0,column=0) + stk.Radiobutton(self.frm_actions,value=2,text="verify",variable=self.action,underline=0).grid(row=1,column=0) + tk.Button(self.frm_actions,text="Refresh",underline=0,command=self.load_data_into_form).grid(row=2,column=0) + + # array of fingers + # we want 2 rows, 5 columns + self.frm_fingers = tk.Frame(self.master) + self.frm_fingers.grid(row=1,column=1) + self.fingers = [] + hand = 0 + while hand < 2: + finger = 0 + while finger < 5: + fnum = finger if hand==1 else len(str_fingers)-finger-1 + #print(f"While preparing hand {hand}, evaluating finger {finger}, which might need to be number {fnum}") + tf = str_hands[hand] + "-" + str_fingers[fnum] + self.fingers.append(tk.Button(self.frm_fingers,text=tf,padx=0,pady=0,command=lambda f=tf: self.func_finger_button(f),image=self.img_notenrolled,compound="top")) + self.fingers[-1].grid(row=hand,column=finger) + finger = finger + 1 + hand = hand + 1 + # because the underline business does not work on the radio button itself + # somehow the keypress takes over the lambda first var, so just set a dummy value, and pass our useful value as second parameter. + self.master.bind("",lambda a=1,b=1: self.set_action(a,b)) + self.master.bind("",lambda a=1,b=2: self.set_action(a,b)) + self.master.bind("",self.load_data_into_form) + + # status bar + stk.StatusBar(self.master,var=self.statustext) + # check if user has setusername polkit-1 permission, which lets him control fingerprint enrollments for other users. + temp1 = lib.check_setusername_permission(self.func_update_status) + if self.verbose.get(): + print(f"DEBUG (init): has set_username: {temp1}") + if temp1: + #self.chk_advanced.configure(state="enabled") + #menu_file.child[0].child[0].configure(state="enabled") + menu_file.entryconfigure(0,state="normal") + #print(menu_file.children) + # and now, load data into form for the first time + self.load_data_into_form() + + def set_action(self, keypress, a): + #print(f"DEBUG: got keypress {keypress}, a={a}") + # this is used by Alt+E, Alt+V to select the radio buttons for enroll, verify, etc. + self.action.set(a) + + def get_used_user(self): + advanced = self.advanced.get() + if advanced: + used_user = self.username.get() + else: + used_user = self.current_username + return used_user + + def load_data_into_form(self, a = None, b = None, c = None): + time.sleep(0.05) + advanced = self.advanced.get() + # show advanced toolbar + used_user = self.get_used_user() + if advanced: + if self.verbose.get(): + print(f"DEBUG: showing advanced toolbar") + self.frm_advanced.grid(row=0,column=0,columnspan=100) + else: + if self.verbose.get(): + print(f"DEBUG: hiding advanced toolbar") + self.frm_advanced.grid_forget() + # update enrolled fingers icons + try: + self.old_enrolled_fingers = self.enrolled_fingers + except: + # will happen if self.enrolled_fingers is not present + self.old_enrolled_fingers = [] + temp1 = lib.get_enrolled_fingers(used_user) + if temp1: + self.enrolled_fingers = temp1 + else: + if self.verbose.get(): + print(f"DEBUG (load_data_into_form): having to skip empty response from get_enrolled_fingers") + self.func_update_status(f"Unable to read enrolled fingers for user {used_user}.", reload = False) + if self.verbose.get(): + print(f"DEBUG (load_data_into_form): got user {used_user} enrolled fingers {self.enrolled_fingers}") + for i in self.fingers: + if str(i.cget('text')) in self.enrolled_fingers: + i.config(image=self.img_enrolled) + else: + i.config(image=self.img_notenrolled) + + # functions + def func_about(self): + """ Display about dialog. """ + tk.messagebox.Message(title="About",message=ABOUT_TEXT,icon="info").show() + + def func_exit(self): + # in case we need to manually do stuff + # otherwise command=self.client_exit would have sufficed. + self.master.quit() + + def func_finger_button(self, finger): + if self.verbose.get(): + print(f"DEBUG: func_finger_button finger {finger}, action {self.action.get()}") + action = self.action.get() + used_user = self.get_used_user() + # position in array is same as the value coming from radio button for actions. + available_actions = ["none","enroll","verify"] + try: + action_str = available_actions[action] + except ValueError: + action_str = "OFF" + if action_str in available_actions: + self.last_fingerbutton_used.set(finger) + try: + t1 = threading.Thread(target=lib.fprintd_action, args=(action_str, finger, self.func_update_status, used_user)) + t1.start() + except Exception as e: + self.statustext.set(e) + else: + self.statustext.set(f"Invalid action {action}, string {action_str}.") + # This blocks everything! Do not use this. + #t1.join() + # unfortunately useless here, because of the threading. + #self.load_data_into_form() + + def func_update_status(self, msg, reload = True): + msg = msg.strip() + if self.verbose.get(): + print(f"DEBUG (func_update_status): msg {msg}",file=sys.stderr) + self.statustext.set(msg) + try: + this_finger = [i for i in self.fingers if i.cget("text") == self.last_fingerbutton_used.get()][0] + except Exception as e: + this_finger = None + if self.verbose.get(): + print(f"DEBUG (func_update_status): while evaluating last-used finger, got {e}") + # flash these red + failure_messages = [ + "enroll-duplicate", + "verify-no-match" + ] + # flash these green + success_messages = [ + "verify-match", + "enroll-completed" + ] + flashed = False + for i in failure_messages: + if i in msg: + flashed = True + if this_finger: + stk.flash_entry(this_finger,["red",self.background_color]*3,300) + break + if not flashed: + for i in success_messages: + if i in msg: + if this_finger: + stk.flash_entry(this_finger,["green",self.background_color]*3,300) + break + if reload: + self.load_data_into_form() + + def func_delete(self): + used_user = self.get_used_user() + sure = tkinter.messagebox.askokcancel(title="Delete all enrolled fingerprints",message=f"Are you sure you want to delete all enrolled fingerprints for user {used_user}?") + if sure: + t1 = threading.Thread(target=lib.fprintd_action, args=("delete", "none", self.func_update_status, used_user)) + t1.start() + else: + self.statustext.set("Cancelled the delete action.") + +# main +root = tk.Tk() +fprintd_tk = App(root) +fprintd_tk.mainloop() -- cgit