diff --git a/howdy-gtk/authsticky.py b/howdy-gtk/authsticky.py new file mode 100644 index 0000000..0e3e040 --- /dev/null +++ b/howdy-gtk/authsticky.py @@ -0,0 +1,125 @@ +# Shows a floating window when authenticating +import cairo +import gi +import signal +import sys +import os + +# Make sure we have the libs we need +gi.require_version("Gtk", "3.0") +gi.require_version("Gdk", "3.0") + +# Import them +from gi.repository import Gtk as gtk +from gi.repository import Gdk as gdk +from gi.repository import GObject as gobject + +# Set window size constants +windowWidth = 400 +windowHeight = 100 + +message = "Starting up... " +subtext = "" + + +class StickyWindow(gtk.Window): + def __init__(self): + gtk.Window.__init__(self) + + self.set_title("Howdy Authentication UI") + + # Set a bunch of options to make the window stick and be on top of everything + self.stick() + self.set_gravity(gdk.Gravity.STATIC) + self.set_resizable(False) + self.set_keep_above(True) + self.set_app_paintable(True) + self.set_skip_pager_hint(True) + self.set_skip_taskbar_hint(True) + self.set_can_focus(False) + self.set_can_default(False) + self.set_focus(None) + self.set_type_hint(gdk.WindowTypeHint.NOTIFICATION) + self.set_decorated(False) + + # Draw + self.connect("draw", self.draw) + self.connect("destroy", self.exit) + self.connect("button-press-event", self.exit) + + darea = gtk.DrawingArea() + darea.set_size_request(windowWidth, windowHeight) + self.add(darea) + + screen = self.get_screen() + visual = screen.get_rgba_visual() + if visual and screen.is_composited(): + self.set_visual(visual) + + # TODO: handle more than 1 screen + self.move((screen.get_width() / 2) - (windowWidth / 2), 0) + + self.show_all() + self.resize(windowWidth, windowHeight) + + gobject.timeout_add(100, self.test) + + gtk.main() + + def draw(self, widget, ctx): + # Change cursor to the kill icon + self.get_window().set_cursor(gdk.Cursor(gdk.CursorType.PIRATE)) + + ctx.set_source_rgba(0, 0, 0, .9) + ctx.set_operator(cairo.OPERATOR_SOURCE) + ctx.paint() + ctx.set_operator(cairo.OPERATOR_OVER) + + dir = os.path.dirname(os.path.abspath(__file__)) + + image_surface = cairo.ImageSurface.create_from_png(dir + "/logo.png") + ratio = float(windowHeight - 20) / float(image_surface.get_height()) + + ctx.translate(15, 10) + ctx.scale(ratio, ratio) + ctx.set_source_surface(image_surface) + ctx.paint() + + ctx.set_source_rgba(255, 255, 255, .9) + ctx.set_font_size(80) + + if subtext: + ctx.move_to(380, 145) + else: + ctx.move_to(380, 170) + + ctx.select_font_face("Arial", cairo.FONT_SLANT_NORMAL, cairo.FONT_WEIGHT_NORMAL) + ctx.show_text(message) + + ctx.set_source_rgba(230, 230, 230, .8) + ctx.set_font_size(40) + ctx.move_to(380, 210) + ctx.select_font_face("Arial", cairo.FONT_SLANT_NORMAL, cairo.FONT_WEIGHT_NORMAL) + ctx.show_text(subtext) + + def exit(self, widget, context): + gtk.main_quit() + + def test(self): + global message, subtext + + comm = sys.stdin.readline()[:-1] + + if comm: + if comm[0] == "M": + message = comm[2:] + if comm[0] == "S": + subtext = comm[2:] + + self.queue_draw() + gobject.timeout_add(100, self.test) + + +signal.signal(signal.SIGINT, signal.SIG_DFL) + +window = StickyWindow() diff --git a/howdy-gtk/logo.png b/howdy-gtk/logo.png new file mode 100644 index 0000000..78c620b Binary files /dev/null and b/howdy-gtk/logo.png differ diff --git a/src/compare.py b/src/compare.py index d0ea5ea..8427363 100644 --- a/src/compare.py +++ b/src/compare.py @@ -17,12 +17,21 @@ import configparser import dlib import cv2 import datetime +import subprocess import snapshot import numpy as np import _thread as thread from recorders.video_capture import VideoCapture +def exit(code): + """Exit while closeing howdy-gtk properly""" + global gtk_proc + + gtk_proc.terminate() + sys.exit(code) + + def init_detector(lock): """Start face detector, encoder and predictor in a new thread""" global face_detector, pose_predictor, face_encoder @@ -33,7 +42,7 @@ def init_detector(lock): print("\n\tcd " + PATH + "/dlib-data") print("\tsudo ./install.sh\n") lock.release() - sys.exit(1) + exit(1) # Use the CNN detector if enabled if use_cnn: @@ -62,9 +71,24 @@ def make_snapshot(type): ]) +def send_to_ui(type, message): + """Send message to the auth ui""" + global gtk_proc + + # Format message so the ui can parse it + message = type + "=" + message + " \n" + + # Try to send the message to the auth ui, but it's okay if that fails + try: + gtk_proc.stdin.write(bytearray(message.encode("ascii"))) + gtk_proc.stdin.flush() + except IOError as err: + pass + + # Make sure we were given an username to tast against if len(sys.argv) < 2: - sys.exit(12) + exit(12) # Get the absolute path to the current directory PATH = os.path.abspath(__file__ + "/..") @@ -90,6 +114,11 @@ face_detector = None pose_predictor = None face_encoder = None +# Start the auth ui +gtk_proc = subprocess.Popen(["python3", "-u", "../howdy-gtk/authsticky.py"], stdin=subprocess.PIPE, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) +# Write to the stdin to redraw ui +send_to_ui("M", "Starting up...") + # Try to load the face model from the models folder try: models = json.load(open(PATH + "/models/" + user + ".dat")) @@ -97,11 +126,11 @@ try: for model in models: encodings += model["data"] except FileNotFoundError: - sys.exit(10) + exit(10) # Check if the file contains a model if len(models) < 1: - sys.exit(10) + exit(10) # Read config from disk config = configparser.ConfigParser() @@ -156,18 +185,29 @@ timeout = config.getint("video", "timeout") dark_threshold = config.getfloat("video", "dark_threshold") end_report = config.getboolean("debug", "end_report") +# Initiate histogram equalization +clahe = cv2.createCLAHE(clipLimit=2.0, tileGridSize=(8, 8)) + +# Let the ui know that we're ready +send_to_ui("M", "Identifying you...") + # Start the read loop frames = 0 valid_frames = 0 timings["fr"] = time.time() dark_running_total = 0 -clahe = cv2.createCLAHE(clipLimit=2.0, tileGridSize=(8, 8)) - while True: # Increment the frame count every loop frames += 1 + # Form a string to let the user know we're real busy + ui_subtext = "Scanned " + str(valid_frames) + " frames" + if (dark_tries > 1): + ui_subtext += " (skipped " + str(dark_tries) + " dark frames)" + # Show it in the ui as subtext + send_to_ui("S", ui_subtext) + # Stop if we've exceded the time limit if time.time() - timings["fr"] > timeout: # Create a timeout snapshot if enabled @@ -177,9 +217,9 @@ while True: if dark_tries == valid_frames: print("All frames were too dark, please check dark_threshold in config") print("Average darkness: " + str(dark_running_total / max(1, valid_frames)) + ", Threshold: " + str(dark_threshold)) - sys.exit(13) + exit(13) else: - sys.exit(11) + exit(11) # Grab a single frame of video frame, gsframe = video_capture.read_frame() @@ -283,7 +323,7 @@ while True: make_snapshot("SUCCESSFUL") # End peacefully - sys.exit(0) + exit(0) if exposure != -1: # For a strange reason on some cameras (e.g. Lenoxo X1E)