aboutsummaryrefslogtreecommitdiff
path: root/fprintd_tk
diff options
context:
space:
mode:
authorB. Stack <bgstack15@gmail.com>2024-09-24 19:57:24 -0400
committerB. Stack <bgstack15@gmail.com>2024-09-24 19:57:24 -0400
commit0f9ad0d0b810def89705d4c7fbbff962b16ba4c4 (patch)
tree3061220ed78cfe4f4c137e5973c451dd307f1bca /fprintd_tk
parentadd image to readme (diff)
downloadfprintd-tk-master.tar.gz
fprintd-tk-master.tar.bz2
fprintd-tk-master.zip
dpkg build recipe and assetsHEADmaster
Diffstat (limited to 'fprintd_tk')
-rwxr-xr-xfprintd_tk262
1 files changed, 262 insertions, 0 deletions
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("<Alt-e>",lambda a=1,b=1: self.set_action(a,b))
+ self.master.bind("<Alt-v>",lambda a=1,b=2: self.set_action(a,b))
+ self.master.bind("<Alt-r>",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()
bgstack15