First working implementation of auth ui in gtk
This commit is contained in:
parent
3c9537a35f
commit
d154c56566
3 changed files with 174 additions and 9 deletions
125
howdy-gtk/authsticky.py
Normal file
125
howdy-gtk/authsticky.py
Normal file
|
|
@ -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()
|
||||||
BIN
howdy-gtk/logo.png
Normal file
BIN
howdy-gtk/logo.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 13 KiB |
|
|
@ -17,12 +17,21 @@ import configparser
|
||||||
import dlib
|
import dlib
|
||||||
import cv2
|
import cv2
|
||||||
import datetime
|
import datetime
|
||||||
|
import subprocess
|
||||||
import snapshot
|
import snapshot
|
||||||
import numpy as np
|
import numpy as np
|
||||||
import _thread as thread
|
import _thread as thread
|
||||||
from recorders.video_capture import VideoCapture
|
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):
|
def init_detector(lock):
|
||||||
"""Start face detector, encoder and predictor in a new thread"""
|
"""Start face detector, encoder and predictor in a new thread"""
|
||||||
global face_detector, pose_predictor, face_encoder
|
global face_detector, pose_predictor, face_encoder
|
||||||
|
|
@ -33,7 +42,7 @@ def init_detector(lock):
|
||||||
print("\n\tcd " + PATH + "/dlib-data")
|
print("\n\tcd " + PATH + "/dlib-data")
|
||||||
print("\tsudo ./install.sh\n")
|
print("\tsudo ./install.sh\n")
|
||||||
lock.release()
|
lock.release()
|
||||||
sys.exit(1)
|
exit(1)
|
||||||
|
|
||||||
# Use the CNN detector if enabled
|
# Use the CNN detector if enabled
|
||||||
if use_cnn:
|
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
|
# Make sure we were given an username to tast against
|
||||||
if len(sys.argv) < 2:
|
if len(sys.argv) < 2:
|
||||||
sys.exit(12)
|
exit(12)
|
||||||
|
|
||||||
# Get the absolute path to the current directory
|
# Get the absolute path to the current directory
|
||||||
PATH = os.path.abspath(__file__ + "/..")
|
PATH = os.path.abspath(__file__ + "/..")
|
||||||
|
|
@ -90,6 +114,11 @@ face_detector = None
|
||||||
pose_predictor = None
|
pose_predictor = None
|
||||||
face_encoder = 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 to load the face model from the models folder
|
||||||
try:
|
try:
|
||||||
models = json.load(open(PATH + "/models/" + user + ".dat"))
|
models = json.load(open(PATH + "/models/" + user + ".dat"))
|
||||||
|
|
@ -97,11 +126,11 @@ try:
|
||||||
for model in models:
|
for model in models:
|
||||||
encodings += model["data"]
|
encodings += model["data"]
|
||||||
except FileNotFoundError:
|
except FileNotFoundError:
|
||||||
sys.exit(10)
|
exit(10)
|
||||||
|
|
||||||
# Check if the file contains a model
|
# Check if the file contains a model
|
||||||
if len(models) < 1:
|
if len(models) < 1:
|
||||||
sys.exit(10)
|
exit(10)
|
||||||
|
|
||||||
# Read config from disk
|
# Read config from disk
|
||||||
config = configparser.ConfigParser()
|
config = configparser.ConfigParser()
|
||||||
|
|
@ -156,18 +185,29 @@ timeout = config.getint("video", "timeout")
|
||||||
dark_threshold = config.getfloat("video", "dark_threshold")
|
dark_threshold = config.getfloat("video", "dark_threshold")
|
||||||
end_report = config.getboolean("debug", "end_report")
|
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
|
# Start the read loop
|
||||||
frames = 0
|
frames = 0
|
||||||
valid_frames = 0
|
valid_frames = 0
|
||||||
timings["fr"] = time.time()
|
timings["fr"] = time.time()
|
||||||
dark_running_total = 0
|
dark_running_total = 0
|
||||||
|
|
||||||
clahe = cv2.createCLAHE(clipLimit=2.0, tileGridSize=(8, 8))
|
|
||||||
|
|
||||||
while True:
|
while True:
|
||||||
# Increment the frame count every loop
|
# Increment the frame count every loop
|
||||||
frames += 1
|
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
|
# Stop if we've exceded the time limit
|
||||||
if time.time() - timings["fr"] > timeout:
|
if time.time() - timings["fr"] > timeout:
|
||||||
# Create a timeout snapshot if enabled
|
# Create a timeout snapshot if enabled
|
||||||
|
|
@ -177,9 +217,9 @@ while True:
|
||||||
if dark_tries == valid_frames:
|
if dark_tries == valid_frames:
|
||||||
print("All frames were too dark, please check dark_threshold in config")
|
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))
|
print("Average darkness: " + str(dark_running_total / max(1, valid_frames)) + ", Threshold: " + str(dark_threshold))
|
||||||
sys.exit(13)
|
exit(13)
|
||||||
else:
|
else:
|
||||||
sys.exit(11)
|
exit(11)
|
||||||
|
|
||||||
# Grab a single frame of video
|
# Grab a single frame of video
|
||||||
frame, gsframe = video_capture.read_frame()
|
frame, gsframe = video_capture.read_frame()
|
||||||
|
|
@ -283,7 +323,7 @@ while True:
|
||||||
make_snapshot("SUCCESSFUL")
|
make_snapshot("SUCCESSFUL")
|
||||||
|
|
||||||
# End peacefully
|
# End peacefully
|
||||||
sys.exit(0)
|
exit(0)
|
||||||
|
|
||||||
if exposure != -1:
|
if exposure != -1:
|
||||||
# For a strange reason on some cameras (e.g. Lenoxo X1E)
|
# For a strange reason on some cameras (e.g. Lenoxo X1E)
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue