From d154c56566b0fc20c9ed1e1c59bb5ede26fecee3 Mon Sep 17 00:00:00 2001 From: boltgolt Date: Thu, 3 Dec 2020 00:36:22 +0100 Subject: [PATCH 01/13] First working implementation of auth ui in gtk --- howdy-gtk/authsticky.py | 125 ++++++++++++++++++++++++++++++++++++++++ howdy-gtk/logo.png | Bin 0 -> 13307 bytes src/compare.py | 58 ++++++++++++++++--- 3 files changed, 174 insertions(+), 9 deletions(-) create mode 100644 howdy-gtk/authsticky.py create mode 100644 howdy-gtk/logo.png 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 0000000000000000000000000000000000000000..78c620b37b91f6bf98ebfc0d63fd8defa2e295ea GIT binary patch literal 13307 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 literal 0 HcmV?d00001 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) From 2c28c943536fcfa4e388399aef1176323587bfd8 Mon Sep 17 00:00:00 2001 From: boltgolt Date: Thu, 3 Dec 2020 01:19:15 +0100 Subject: [PATCH 02/13] Made howdy-gtk debian package --- debian/control | 2 +- howdy-gtk/debian/changelog | 5 +++++ howdy-gtk/debian/compat | 1 + howdy-gtk/debian/control | 13 ++++++++++++ howdy-gtk/debian/copyright | 21 +++++++++++++++++++ howdy-gtk/debian/howdy-gtk.links | 1 + howdy-gtk/debian/howdy-gtk.lintian-overrides | 2 ++ howdy-gtk/debian/install | 1 + howdy-gtk/debian/rules | 8 +++++++ howdy-gtk/debian/source/format | 1 + howdy-gtk/debian/source/options | 8 +++++++ howdy-gtk/{ => src}/authsticky.py | 4 +++- howdy-gtk/{ => src}/logo.png | Bin src/compare.py | 4 ++-- 14 files changed, 67 insertions(+), 4 deletions(-) create mode 100644 howdy-gtk/debian/changelog create mode 100644 howdy-gtk/debian/compat create mode 100644 howdy-gtk/debian/control create mode 100644 howdy-gtk/debian/copyright create mode 100644 howdy-gtk/debian/howdy-gtk.links create mode 100644 howdy-gtk/debian/howdy-gtk.lintian-overrides create mode 100644 howdy-gtk/debian/install create mode 100755 howdy-gtk/debian/rules create mode 100644 howdy-gtk/debian/source/format create mode 100644 howdy-gtk/debian/source/options rename howdy-gtk/{ => src}/authsticky.py (96%) mode change 100644 => 100755 rename howdy-gtk/{ => src}/logo.png (100%) diff --git a/debian/control b/debian/control index 0d2ae80..76bb599 100644 --- a/debian/control +++ b/debian/control @@ -10,7 +10,7 @@ Package: howdy Homepage: https://github.com/boltgolt/howdy Architecture: all Depends: ${misc:Depends}, curl|wget, python3, python3-pip, python3-dev, python3-setuptools, libpam-python, libopencv-dev, cmake -Recommends: libatlas-base-dev | libopenblas-dev | liblapack-dev, streamer +Recommends: libatlas-base-dev | libopenblas-dev | liblapack-dev, streamer, howdy-gtk Suggests: nvidia-cuda-dev (>= 7.5) Description: Howdy: Windows Hello style authentication for Linux. Use your built-in IR emitters and camera in combination with face recognition diff --git a/howdy-gtk/debian/changelog b/howdy-gtk/debian/changelog new file mode 100644 index 0000000..627952a --- /dev/null +++ b/howdy-gtk/debian/changelog @@ -0,0 +1,5 @@ +howdy-gtk (0.0.1) xenial; urgency=medium + + * Initial testing release with sticky authentication ui + + -- boltgolt Thu, 03 Dec 2020 00:08:49 +0200 diff --git a/howdy-gtk/debian/compat b/howdy-gtk/debian/compat new file mode 100644 index 0000000..f599e28 --- /dev/null +++ b/howdy-gtk/debian/compat @@ -0,0 +1 @@ +10 diff --git a/howdy-gtk/debian/control b/howdy-gtk/debian/control new file mode 100644 index 0000000..e74a4fb --- /dev/null +++ b/howdy-gtk/debian/control @@ -0,0 +1,13 @@ +Source: howdy-gtk +Section: misc +Priority: optional +Standards-Version: 3.9.7 +Build-Depends: python, dh-python, devscripts, dh-make, debhelper, fakeroot +Maintainer: boltgolt +Vcs-Git: https://github.com/boltgolt/howdy + +Package: howdy-gtk +Homepage: https://github.com/boltgolt/howdy +Architecture: all +Depends: ${misc:Depends}, curl|wget, python3, python3-pip, python3-dev, python-gtk2, cmake +Description: Optional UI package for Howdy, written in Gtk diff --git a/howdy-gtk/debian/copyright b/howdy-gtk/debian/copyright new file mode 100644 index 0000000..38c6a9a --- /dev/null +++ b/howdy-gtk/debian/copyright @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2020 boltgolt + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/howdy-gtk/debian/howdy-gtk.links b/howdy-gtk/debian/howdy-gtk.links new file mode 100644 index 0000000..07963da --- /dev/null +++ b/howdy-gtk/debian/howdy-gtk.links @@ -0,0 +1 @@ +/usr/lib/howdy-gtk/authsticky.py /usr/bin/howdy-gtk-auth diff --git a/howdy-gtk/debian/howdy-gtk.lintian-overrides b/howdy-gtk/debian/howdy-gtk.lintian-overrides new file mode 100644 index 0000000..250017d --- /dev/null +++ b/howdy-gtk/debian/howdy-gtk.lintian-overrides @@ -0,0 +1,2 @@ +# W: Don't require ugly linebreaks in last 5 chars +howdy: debian-changelog-line-too-long diff --git a/howdy-gtk/debian/install b/howdy-gtk/debian/install new file mode 100644 index 0000000..279038f --- /dev/null +++ b/howdy-gtk/debian/install @@ -0,0 +1 @@ +src/. /usr/lib/howdy-gtk diff --git a/howdy-gtk/debian/rules b/howdy-gtk/debian/rules new file mode 100755 index 0000000..08b0880 --- /dev/null +++ b/howdy-gtk/debian/rules @@ -0,0 +1,8 @@ +#!/usr/bin/make -f +DH_VERBOSE = 1 + +DPKG_EXPORT_BUILDFLAGS = 1 +include /usr/share/dpkg/default.mk + +%: + dh $@ diff --git a/howdy-gtk/debian/source/format b/howdy-gtk/debian/source/format new file mode 100644 index 0000000..89ae9db --- /dev/null +++ b/howdy-gtk/debian/source/format @@ -0,0 +1 @@ +3.0 (native) diff --git a/howdy-gtk/debian/source/options b/howdy-gtk/debian/source/options new file mode 100644 index 0000000..1d6e494 --- /dev/null +++ b/howdy-gtk/debian/source/options @@ -0,0 +1,8 @@ +tar-ignore = ".git" +tar-ignore = ".gitignore" +tar-ignore = ".github" +tar-ignore = "README.md" +tar-ignore = ".travis.yml" +tar-ignore = "fedora" +tar-ignore = "opensuse" +tar-ignore = "archlinux" diff --git a/howdy-gtk/authsticky.py b/howdy-gtk/src/authsticky.py old mode 100644 new mode 100755 similarity index 96% rename from howdy-gtk/authsticky.py rename to howdy-gtk/src/authsticky.py index 0e3e040..3f973d2 --- a/howdy-gtk/authsticky.py +++ b/howdy-gtk/src/authsticky.py @@ -1,3 +1,5 @@ +#!/usr/bin/env python3 + # Shows a floating window when authenticating import cairo import gi @@ -77,7 +79,7 @@ class StickyWindow(gtk.Window): dir = os.path.dirname(os.path.abspath(__file__)) - image_surface = cairo.ImageSurface.create_from_png(dir + "/logo.png") + image_surface = cairo.ImageSurface.create_from_png("/usr/lib/howdy-gtk/logo.png") ratio = float(windowHeight - 20) / float(image_surface.get_height()) ctx.translate(15, 10) diff --git a/howdy-gtk/logo.png b/howdy-gtk/src/logo.png similarity index 100% rename from howdy-gtk/logo.png rename to howdy-gtk/src/logo.png diff --git a/src/compare.py b/src/compare.py index 8427363..46d0ba1 100644 --- a/src/compare.py +++ b/src/compare.py @@ -115,7 +115,7 @@ 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) +gtk_proc = subprocess.Popen(["howdy-gtk-auth"], stdin=subprocess.PIPE, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) # Write to the stdin to redraw ui send_to_ui("M", "Starting up...") @@ -202,7 +202,7 @@ while True: frames += 1 # Form a string to let the user know we're real busy - ui_subtext = "Scanned " + str(valid_frames) + " frames" + ui_subtext = "Scanned " + str(valid_frames - dark_tries) + " frames" if (dark_tries > 1): ui_subtext += " (skipped " + str(dark_tries) + " dark frames)" # Show it in the ui as subtext From 11eaccbdfbb06db2ba055cfaf17645683d10066f Mon Sep 17 00:00:00 2001 From: boltgolt Date: Thu, 3 Dec 2020 13:08:37 +0100 Subject: [PATCH 03/13] Cleanup --- howdy-gtk/debian/control | 2 +- howdy-gtk/src/authsticky.py | 205 +++++++++++++++++++++--------------- src/compare.py | 30 ++++-- 3 files changed, 141 insertions(+), 96 deletions(-) diff --git a/howdy-gtk/debian/control b/howdy-gtk/debian/control index e74a4fb..c192788 100644 --- a/howdy-gtk/debian/control +++ b/howdy-gtk/debian/control @@ -9,5 +9,5 @@ Vcs-Git: https://github.com/boltgolt/howdy Package: howdy-gtk Homepage: https://github.com/boltgolt/howdy Architecture: all -Depends: ${misc:Depends}, curl|wget, python3, python3-pip, python3-dev, python-gtk2, cmake +Depends: ${misc:Depends}, curl|wget, python3, python3-pip, python3-dev, python-gtk2, python-gtk2-dev, cmake Description: Optional UI package for Howdy, written in Gtk diff --git a/howdy-gtk/src/authsticky.py b/howdy-gtk/src/authsticky.py index 3f973d2..4d36059 100755 --- a/howdy-gtk/src/authsticky.py +++ b/howdy-gtk/src/authsticky.py @@ -20,108 +20,143 @@ from gi.repository import GObject as gobject windowWidth = 400 windowHeight = 100 +# Set default messages to show in the popup message = "Starting up... " subtext = "" class StickyWindow(gtk.Window): - def __init__(self): - gtk.Window.__init__(self) + def __init__(self): + """Initialize the sticky window""" + # Make the class a GTK window + gtk.Window.__init__(self) - self.set_title("Howdy Authentication UI") + # Set the title of the window + 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) + # 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) + # Listen for a window redraw + self.connect("draw", self.draw) + # Listen for a force close or click event and exit + self.connect("destroy", self.exit) + self.connect("button-press-event", self.exit) - darea = gtk.DrawingArea() - darea.set_size_request(windowWidth, windowHeight) - self.add(darea) + # Create a GDK drawing, restricts the window size + 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) + # Get the default screen + screen = gdk.Screen.get_default() + 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) + # Move the window to the center top of the default window, where a webcam usually is + self.move((screen.get_width() / 2) - (windowWidth / 2), 0) - self.show_all() - self.resize(windowWidth, windowHeight) + # Show window and force a resize again + self.show_all() + self.resize(windowWidth, windowHeight) - gobject.timeout_add(100, self.test) + print("init") - gtk.main() + # Add a timeout to catch input passed from compare.py + gobject.timeout_add(100, self.catch_stdin) - 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("/usr/lib/howdy-gtk/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) + # Start GTK main loop + gtk.main() + def draw(self, widget, ctx): + """Draw the UI""" + # Change cursor to the kill icon + self.get_window().set_cursor(gdk.Cursor(gdk.CursorType.PIRATE)) + + # Draw a semi transparent background + ctx.set_source_rgba(0, 0, 0, .7) + ctx.set_operator(cairo.OPERATOR_SOURCE) + ctx.paint() + ctx.set_operator(cairo.OPERATOR_OVER) + + # Get absolute or relative logo path + path = "/usr/lib/howdy-gtk/logo.png" + if not os.access(path, os.R_OK): + path = "./logo.png" + + # Create image and calculate scale size based on image size + image_surface = cairo.ImageSurface.create_from_png(path) + ratio = float(windowHeight - 20) / float(image_surface.get_height()) + + # Position and draw the logo + ctx.translate(15, 10) + ctx.scale(ratio, ratio) + ctx.set_source_surface(image_surface) + ctx.paint() + + # Calculate main message positioning, as the text is heigher if there's a subtext + if subtext: + ctx.move_to(380, 145) + else: + ctx.move_to(380, 170) + + # Draw the main message + ctx.set_source_rgba(255, 255, 255, .9) + ctx.set_font_size(80) + ctx.select_font_face("Arial", cairo.FONT_SLANT_NORMAL, cairo.FONT_WEIGHT_NORMAL) + ctx.show_text(message) + + # Draw the subtext if there is one + if subtext: + ctx.move_to(380, 210) + ctx.set_source_rgba(230, 230, 230, .8) + ctx.set_font_size(40) + ctx.select_font_face("Arial", cairo.FONT_SLANT_NORMAL, cairo.FONT_WEIGHT_NORMAL) + ctx.show_text(subtext) + + + def catch_stdin(self): + """Catch input from stdin and redraw""" + global message, subtext + + # Wait for a line on stdin + comm = sys.stdin.readline()[:-1] + + # If the line is not empty + if comm: + # Parse a message + if comm[0] == "M": + message = comm[2:] + # Parse subtext + if comm[0] == "S": + subtext = comm[2:] + + # Redraw the ui + self.queue_draw() + + # Fire this function again in 10ms, as we're waiting on IO in readline anyway + gobject.timeout_add(10, self.catch_stdin) + + + def exit(self, widget, context): + """Cleanly exit""" + gtk.main_quit() + +# Make sure we quit on a SIGINT signal.signal(signal.SIGINT, signal.SIG_DFL) +# Open the GTK window window = StickyWindow() diff --git a/src/compare.py b/src/compare.py index 46d0ba1..f766bde 100644 --- a/src/compare.py +++ b/src/compare.py @@ -28,7 +28,11 @@ def exit(code): """Exit while closeing howdy-gtk properly""" global gtk_proc - gtk_proc.terminate() + # Exit the auth ui process if there is one + if 'gtk_proc' in globals(): + gtk_proc.terminate() + + # Exit compare sys.exit(code) @@ -75,15 +79,17 @@ 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" + # Only execute of the proccess started + if 'gtk_proc' in globals(): + # 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 + # 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 @@ -115,7 +121,11 @@ pose_predictor = None face_encoder = None # Start the auth ui -gtk_proc = subprocess.Popen(["howdy-gtk-auth"], stdin=subprocess.PIPE, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) +try: + gtk_proc = subprocess.Popen(["python3", "-u", "../howdy-gtk/src/authsticky.py"], stdin=subprocess.PIPE, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) +except FileNotFoundError as err: + pass + # Write to the stdin to redraw ui send_to_ui("M", "Starting up...") From a151338dac849182767e637c429875aab2ac80d0 Mon Sep 17 00:00:00 2001 From: boltgolt Date: Sat, 5 Dec 2020 13:44:40 +0100 Subject: [PATCH 04/13] Started on glade ui, removed howdy-gtk-auth symlink --- howdy-gtk/debian/howdy-gtk.links | 2 +- howdy-gtk/src/authsticky.py | 4 - howdy-gtk/src/init.py | 8 + howdy-gtk/src/ui.glade | 264 +++++++++++++++++++++++++++++++ howdy-gtk/src/window.py | 80 ++++++++++ src/compare.py | 2 +- 6 files changed, 354 insertions(+), 6 deletions(-) mode change 100755 => 100644 howdy-gtk/src/authsticky.py create mode 100755 howdy-gtk/src/init.py create mode 100644 howdy-gtk/src/ui.glade create mode 100644 howdy-gtk/src/window.py diff --git a/howdy-gtk/debian/howdy-gtk.links b/howdy-gtk/debian/howdy-gtk.links index 07963da..a5a7a74 100644 --- a/howdy-gtk/debian/howdy-gtk.links +++ b/howdy-gtk/debian/howdy-gtk.links @@ -1 +1 @@ -/usr/lib/howdy-gtk/authsticky.py /usr/bin/howdy-gtk-auth +/usr/lib/howdy-gtk/init.py /usr/bin/howdy-gtk diff --git a/howdy-gtk/src/authsticky.py b/howdy-gtk/src/authsticky.py old mode 100755 new mode 100644 index 4d36059..8c4d964 --- a/howdy-gtk/src/authsticky.py +++ b/howdy-gtk/src/authsticky.py @@ -1,5 +1,3 @@ -#!/usr/bin/env python3 - # Shows a floating window when authenticating import cairo import gi @@ -72,8 +70,6 @@ class StickyWindow(gtk.Window): self.show_all() self.resize(windowWidth, windowHeight) - print("init") - # Add a timeout to catch input passed from compare.py gobject.timeout_add(100, self.catch_stdin) diff --git a/howdy-gtk/src/init.py b/howdy-gtk/src/init.py new file mode 100755 index 0000000..01a7bb3 --- /dev/null +++ b/howdy-gtk/src/init.py @@ -0,0 +1,8 @@ +#!/usr/bin/env python3 +# Opens auth ui if requested, otherwise starts normal ui +import sys + +if "--start-auth-ui" in sys.argv: + import authsticky +else: + import window diff --git a/howdy-gtk/src/ui.glade b/howdy-gtk/src/ui.glade new file mode 100644 index 0000000..29df8f1 --- /dev/null +++ b/howdy-gtk/src/ui.glade @@ -0,0 +1,264 @@ + + + + + + False + Howdy Configuration + center + logo.png + + + True + True + + + True + True + vertical + + + True + False + 5 + 10 + + + True + False + center + 10 + Active user: + + + False + False + 0 + + + + + True + False + + + False + False + 1 + + + + + Add new model + True + True + True + 10 + 0.46000000834465027 + + + False + False + end + 2 + + + + + Add new user + True + True + True + bottom + + + False + False + end + 3 + + + + + False + True + + + + + True + False + vertical + + + True + False + 5 + + + False + True + 0 + + + + + True + True + + + + + + False + True + 1 + + + + + True + True + + + + + True + + + + + True + False + Models + + + 1 + False + + + + + True + False + vertical + + + True + False + label + + + False + True + 0 + + + + + + + + + + + 1 + + + + + True + False + Video + + + 1 + False + + + + + + + + + + + + + True + False + 10 + 10 + 5 + 5 + vertical + 5 + + + True + False + + + True + False + vertical + + + True + False + label + 3 + + + + + + False + True + 0 + + + + + True + False + label + + + False + True + 1 + + + + + False + True + 0 + + + + + Delete + True + True + True + True + + + False + True + end + 1 + + + + + False + True + 0 + + + + diff --git a/howdy-gtk/src/window.py b/howdy-gtk/src/window.py new file mode 100644 index 0000000..a6d780f --- /dev/null +++ b/howdy-gtk/src/window.py @@ -0,0 +1,80 @@ +# Opens and controls main ui window +import cairo +import gi +import signal +import sys +import os +import subprocess + +# 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 + + +class MainWindow(gtk.Window): + def __init__(self): + """Initialize the sticky window""" + # Make the class a GTK window + gtk.Window.__init__(self) + + self.connect("destroy", self.exit) + + self.builder = gtk.Builder() + self.builder.add_from_file("./ui.glade") + self.builder.connect_signals(self) + + self.window = self.builder.get_object("mainwindow") + self.userlist = self.builder.get_object("userlist") + self.modellistbox = self.builder.get_object("modellistbox") + + filelist = os.listdir("/lib/security/howdy/models") + self.active_user = "" + + for file in filelist: + self.userlist.append_text(file[:-4]) + + if not self.active_user: + self.active_user = file[:-4] + + self.userlist.set_active(0) + + self.load_model_list() + + self.window.show_all() + # self.resize(300, 300) + + # Start GTK main loop + gtk.main() + + def load_model_list(self): + output = subprocess.check_output(["howdy", "list", "-U", self.active_user]) + + lines = output.decode("utf-8") .split("\n")[3:-2] + print(lines) + + newrow = self.builder.get_object("modelrow") + + print(newrow.set_name("wat")) + + self.modellistbox.add(newrow) + newrow2 = self.builder.get_object("modelrow") + + # print(newrow.get_object("modelrowname")) + + self.modellistbox.add(newrow2) + + def exit(self, widget, context): + """Cleanly exit""" + gtk.main_quit() + sys.exit() + + +# Make sure we quit on a SIGINT +signal.signal(signal.SIGINT, signal.SIG_DFL) + +# Open the GTK window +window = MainWindow() diff --git a/src/compare.py b/src/compare.py index f766bde..770b75a 100644 --- a/src/compare.py +++ b/src/compare.py @@ -122,7 +122,7 @@ face_encoder = None # Start the auth ui try: - gtk_proc = subprocess.Popen(["python3", "-u", "../howdy-gtk/src/authsticky.py"], stdin=subprocess.PIPE, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) + gtk_proc = subprocess.Popen(["howdy-gtk", "--start-auth-ui"], stdin=subprocess.PIPE, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) except FileNotFoundError as err: pass From 982641b92bd5a1e850a55f0d964fa0b91b2e0695 Mon Sep 17 00:00:00 2001 From: boltgolt Date: Fri, 18 Dec 2020 17:42:15 +0100 Subject: [PATCH 05/13] Added GUI listing, adding and deleting of models --- howdy-gtk/debian/postinst | 2 + howdy-gtk/src/authsticky.py | 4 +- howdy-gtk/src/ui.glade | 173 +++++++++++++----------------------- howdy-gtk/src/window.py | 110 ++++++++++++++++++++--- src/cli/remove.py | 2 +- 5 files changed, 167 insertions(+), 124 deletions(-) create mode 100644 howdy-gtk/debian/postinst diff --git a/howdy-gtk/debian/postinst b/howdy-gtk/debian/postinst new file mode 100644 index 0000000..1351668 --- /dev/null +++ b/howdy-gtk/debian/postinst @@ -0,0 +1,2 @@ +#!/bin/sh +pip3 install elevate diff --git a/howdy-gtk/src/authsticky.py b/howdy-gtk/src/authsticky.py index 8c4d964..3f21d07 100644 --- a/howdy-gtk/src/authsticky.py +++ b/howdy-gtk/src/authsticky.py @@ -76,7 +76,6 @@ class StickyWindow(gtk.Window): # Start GTK main loop gtk.main() - def draw(self, widget, ctx): """Draw the UI""" # Change cursor to the kill icon @@ -123,7 +122,6 @@ class StickyWindow(gtk.Window): ctx.select_font_face("Arial", cairo.FONT_SLANT_NORMAL, cairo.FONT_WEIGHT_NORMAL) ctx.show_text(subtext) - def catch_stdin(self): """Catch input from stdin and redraw""" global message, subtext @@ -146,11 +144,11 @@ class StickyWindow(gtk.Window): # Fire this function again in 10ms, as we're waiting on IO in readline anyway gobject.timeout_add(10, self.catch_stdin) - def exit(self, widget, context): """Cleanly exit""" gtk.main_quit() + # Make sure we quit on a SIGINT signal.signal(signal.SIGINT, signal.SIG_DFL) diff --git a/howdy-gtk/src/ui.glade b/howdy-gtk/src/ui.glade index 29df8f1..9ed587b 100644 --- a/howdy-gtk/src/ui.glade +++ b/howdy-gtk/src/ui.glade @@ -4,6 +4,7 @@ False + 5 Howdy Configuration center logo.png @@ -11,24 +12,26 @@ True True + False - + True - True + False vertical - + True False + 5 + 5 5 - 10 + 5 True False center - 10 - Active user: + Showing saved models for: False @@ -40,6 +43,7 @@ True False + False @@ -47,22 +51,6 @@ 1 - - - Add new model - True - True - True - 10 - 0.46000000834465027 - - - False - False - end - 2 - - Add new user @@ -80,19 +68,23 @@ - False - True + False + True + 0 True False + 5 + 5 vertical True False + 5 5 @@ -101,35 +93,74 @@ 0 + + + False + True + 1 + + + + + True + False + 5 + 7 + 5 - + + + + + gtk-add True True - - - + True + True + 0.5899999737739563 + True + False True + end 1 + + + gtk-delete + True + True + True + 5 + True + True + + + + False + True + end + 2 + + - True - True + False + True + end + 2 - - True - True False + 2 Models @@ -185,80 +216,4 @@ - - True - False - 10 - 10 - 5 - 5 - vertical - 5 - - - True - False - - - True - False - vertical - - - True - False - label - 3 - - - - - - False - True - 0 - - - - - True - False - label - - - False - True - 1 - - - - - False - True - 0 - - - - - Delete - True - True - True - True - - - False - True - end - 1 - - - - - False - True - 0 - - - diff --git a/howdy-gtk/src/window.py b/howdy-gtk/src/window.py index a6d780f..e0911d0 100644 --- a/howdy-gtk/src/window.py +++ b/howdy-gtk/src/window.py @@ -5,6 +5,7 @@ import signal import sys import os import subprocess +import elevate # Make sure we have the libs we need gi.require_version("Gtk", "3.0") @@ -31,6 +32,19 @@ class MainWindow(gtk.Window): self.userlist = self.builder.get_object("userlist") self.modellistbox = self.builder.get_object("modellistbox") + # Create a treeview that will list the model data + self.treeview = gtk.TreeView() + + # Set the coloums + for i, column in enumerate(["ID", "Created", "Label"]): + cell = gtk.CellRendererText() + col = gtk.TreeViewColumn(column, cell, text=i) + + self.treeview.append_column(col) + + # Add the treeview + self.modellistbox.add(self.treeview) + filelist = os.listdir("/lib/security/howdy/models") self.active_user = "" @@ -42,8 +56,6 @@ class MainWindow(gtk.Window): self.userlist.set_active(0) - self.load_model_list() - self.window.show_all() # self.resize(300, 300) @@ -51,21 +63,94 @@ class MainWindow(gtk.Window): gtk.main() def load_model_list(self): - output = subprocess.check_output(["howdy", "list", "-U", self.active_user]) + """(Re)load the model list""" - lines = output.decode("utf-8") .split("\n")[3:-2] - print(lines) + # Execute the list commond to get the models + output = subprocess.check_output(["howdy", "list", "--plain", "-U", self.active_user]) - newrow = self.builder.get_object("modelrow") + # Split the output per line + # lines = output.decode("utf-8").split("\n") + lines = output.split("\n") - print(newrow.set_name("wat")) + # Create a datamodel + self.listmodel = gtk.ListStore(str, str, str) - self.modellistbox.add(newrow) - newrow2 = self.builder.get_object("modelrow") + # Add the models to the datamodel + for i in range(len(lines)): + self.listmodel.append(lines[i].split(",")) - # print(newrow.get_object("modelrowname")) + self.treeview.set_model(self.listmodel) - self.modellistbox.add(newrow2) + def on_user_change(self, select): + self.active_user = select.get_active_text() + self.load_model_list() + + def on_model_add(self, select): + dialog = gtk.MessageDialog(parent=self, flags=gtk.DialogFlags.MODAL, type=gtk.MessageType.QUESTION, buttons=gtk.ButtonsType.OK_CANCEL) + dialog.props.text = "Please enter a name for the new model, 24 characters max" + dialog.set_title("Confirm Model Creation") + # create the text input field + entry = gtk.Entry() + # create a horizontal box to pack the entry and a label + hbox = gtk.HBox() + hbox.pack_start(gtk.Label("Model name:"), False, 5, 5) + hbox.pack_end(entry, True, True, 5) + # some secondary text + # add it and show it + dialog.vbox.pack_end(hbox, True, True, 0) + dialog.show_all() + # go go go + response = dialog.run() + + text = entry.get_text() + dialog.destroy() + + if response == gtk.ResponseType.OK: + dialog = gtk.MessageDialog(parent=self, flags=gtk.DialogFlags.MODAL) + dialog.props.text = "Please look directly into the camera" + dialog.set_title("Creating Model") + dialog.show_all() + + status, output = subprocess.getstatusoutput(["howdy add -y -U " + self.active_user]) + + dialog.destroy() + + if status != 1: + dialog = gtk.MessageDialog(parent=self, flags=gtk.DialogFlags.MODAL, type=gtk.MessageType.ERROR, buttons=gtk.ButtonsType.CLOSE) + dialog.props.text = "Error while adding model, error code " + str(status) + ": \n\n" + dialog.format_secondary_text(output) + dialog.set_title("Howdy Error") + dialog.run() + dialog.destroy() + + self.load_model_list() + + def on_model_delete(self, select): + selection = self.treeview.get_selection() + (listmodel, rowlist) = selection.get_selected_rows() + + if len(rowlist) == 1: + id = listmodel.get_value(listmodel.get_iter(rowlist[0]), 0) + name = listmodel.get_value(listmodel.get_iter(rowlist[0]), 2) + + dialog = gtk.MessageDialog(parent=self, flags=gtk.DialogFlags.MODAL, buttons=gtk.ButtonsType.OK_CANCEL) + dialog.props.text = "Are you sure you want to delete model " + id + " (" + name + ")?" + dialog.set_title("Confirm Model Deletion") + response = dialog.run() + dialog.destroy() + + if response == gtk.ResponseType.OK: + status, output = subprocess.getstatusoutput(["howdy remove " + id + " -y -U " + self.active_user]) + + if status != 0: + dialog = gtk.MessageDialog(parent=self, flags=gtk.DialogFlags.MODAL, type=gtk.MessageType.ERROR, buttons=gtk.ButtonsType.CLOSE) + dialog.props.text = "Error while deleting model, error code " + str(status) + ": \n\n" + dialog.format_secondary_text(output) + dialog.set_title("Howdy Error") + dialog.run() + dialog.destroy() + + self.load_model_list() def exit(self, widget, context): """Cleanly exit""" @@ -76,5 +161,8 @@ class MainWindow(gtk.Window): # Make sure we quit on a SIGINT signal.signal(signal.SIGINT, signal.SIG_DFL) +# Make sure we run as sudo +elevate.elevate() + # Open the GTK window window = MainWindow() diff --git a/src/cli/remove.py b/src/cli/remove.py index 45e2036..49b8bce 100644 --- a/src/cli/remove.py +++ b/src/cli/remove.py @@ -61,7 +61,7 @@ for enc in encodings: # Abort if no matching id was found if not found: print("No model with ID " + builtins.howdy_args.argument + " exists for " + user) - sys.exit() + sys.exit(1) # Remove the entire file if this encoding is the only one if len(encodings) == 1: From 54f418419c5b4e12699f76e695ae853ed65e198c Mon Sep 17 00:00:00 2001 From: boltgolt Date: Tue, 22 Dec 2020 14:05:54 +0100 Subject: [PATCH 06/13] Moved handlers to seperate file --- howdy-gtk/src/window.py | 80 +++--------------------------- howdy-gtk/src/window_tab_models.py | 77 ++++++++++++++++++++++++++++ 2 files changed, 84 insertions(+), 73 deletions(-) create mode 100644 howdy-gtk/src/window_tab_models.py diff --git a/howdy-gtk/src/window.py b/howdy-gtk/src/window.py index e0911d0..d2607ff 100644 --- a/howdy-gtk/src/window.py +++ b/howdy-gtk/src/window.py @@ -4,7 +4,6 @@ import gi import signal import sys import os -import subprocess import elevate # Make sure we have the libs we need @@ -66,7 +65,8 @@ class MainWindow(gtk.Window): """(Re)load the model list""" # Execute the list commond to get the models - output = subprocess.check_output(["howdy", "list", "--plain", "-U", self.active_user]) + # output = subprocess.check_output(["howdy", "list", "--plain", "-U", self.active_user]) + output = "1,2020-12-05 14:10:22,sd\n2,2020-12-05 14:22:41,\n3,2020-12-05 14:57:37,Model #3" + self.active_user # Split the output per line # lines = output.decode("utf-8").split("\n") @@ -81,77 +81,6 @@ class MainWindow(gtk.Window): self.treeview.set_model(self.listmodel) - def on_user_change(self, select): - self.active_user = select.get_active_text() - self.load_model_list() - - def on_model_add(self, select): - dialog = gtk.MessageDialog(parent=self, flags=gtk.DialogFlags.MODAL, type=gtk.MessageType.QUESTION, buttons=gtk.ButtonsType.OK_CANCEL) - dialog.props.text = "Please enter a name for the new model, 24 characters max" - dialog.set_title("Confirm Model Creation") - # create the text input field - entry = gtk.Entry() - # create a horizontal box to pack the entry and a label - hbox = gtk.HBox() - hbox.pack_start(gtk.Label("Model name:"), False, 5, 5) - hbox.pack_end(entry, True, True, 5) - # some secondary text - # add it and show it - dialog.vbox.pack_end(hbox, True, True, 0) - dialog.show_all() - # go go go - response = dialog.run() - - text = entry.get_text() - dialog.destroy() - - if response == gtk.ResponseType.OK: - dialog = gtk.MessageDialog(parent=self, flags=gtk.DialogFlags.MODAL) - dialog.props.text = "Please look directly into the camera" - dialog.set_title("Creating Model") - dialog.show_all() - - status, output = subprocess.getstatusoutput(["howdy add -y -U " + self.active_user]) - - dialog.destroy() - - if status != 1: - dialog = gtk.MessageDialog(parent=self, flags=gtk.DialogFlags.MODAL, type=gtk.MessageType.ERROR, buttons=gtk.ButtonsType.CLOSE) - dialog.props.text = "Error while adding model, error code " + str(status) + ": \n\n" - dialog.format_secondary_text(output) - dialog.set_title("Howdy Error") - dialog.run() - dialog.destroy() - - self.load_model_list() - - def on_model_delete(self, select): - selection = self.treeview.get_selection() - (listmodel, rowlist) = selection.get_selected_rows() - - if len(rowlist) == 1: - id = listmodel.get_value(listmodel.get_iter(rowlist[0]), 0) - name = listmodel.get_value(listmodel.get_iter(rowlist[0]), 2) - - dialog = gtk.MessageDialog(parent=self, flags=gtk.DialogFlags.MODAL, buttons=gtk.ButtonsType.OK_CANCEL) - dialog.props.text = "Are you sure you want to delete model " + id + " (" + name + ")?" - dialog.set_title("Confirm Model Deletion") - response = dialog.run() - dialog.destroy() - - if response == gtk.ResponseType.OK: - status, output = subprocess.getstatusoutput(["howdy remove " + id + " -y -U " + self.active_user]) - - if status != 0: - dialog = gtk.MessageDialog(parent=self, flags=gtk.DialogFlags.MODAL, type=gtk.MessageType.ERROR, buttons=gtk.ButtonsType.CLOSE) - dialog.props.text = "Error while deleting model, error code " + str(status) + ": \n\n" - dialog.format_secondary_text(output) - dialog.set_title("Howdy Error") - dialog.run() - dialog.destroy() - - self.load_model_list() - def exit(self, widget, context): """Cleanly exit""" gtk.main_quit() @@ -164,5 +93,10 @@ signal.signal(signal.SIGINT, signal.SIG_DFL) # Make sure we run as sudo elevate.elevate() +import window_tab_models +MainWindow.on_user_change = window_tab_models.on_user_change +MainWindow.on_model_add = window_tab_models.on_model_add +MainWindow.on_model_delete = window_tab_models.on_model_delete + # Open the GTK window window = MainWindow() diff --git a/howdy-gtk/src/window_tab_models.py b/howdy-gtk/src/window_tab_models.py new file mode 100644 index 0000000..a79ab8a --- /dev/null +++ b/howdy-gtk/src/window_tab_models.py @@ -0,0 +1,77 @@ +import subprocess + +from gi.repository import Gtk as gtk + + +def on_user_change(self, select): + self.active_user = select.get_active_text() + self.load_model_list() + + +def on_model_add(self, select): + dialog = gtk.MessageDialog(parent=self, flags=gtk.DialogFlags.MODAL, type=gtk.MessageType.QUESTION, buttons=gtk.ButtonsType.OK_CANCEL) + dialog.props.text = "Please enter a name for the new model, 24 characters max" + dialog.set_title("Confirm Model Creation") + # create the text input field + entry = gtk.Entry() + # create a horizontal box to pack the entry and a label + hbox = gtk.HBox() + hbox.pack_start(gtk.Label("Model name:"), False, 5, 5) + hbox.pack_end(entry, True, True, 5) + # some secondary text + # add it and show it + dialog.vbox.pack_end(hbox, True, True, 0) + dialog.show_all() + # go go go + response = dialog.run() + + text = entry.get_text() + dialog.destroy() + + if response == gtk.ResponseType.OK: + dialog = gtk.MessageDialog(parent=self, flags=gtk.DialogFlags.MODAL) + dialog.props.text = "Please look directly into the camera" + dialog.set_title("Creating Model") + dialog.show_all() + + status, output = subprocess.getstatusoutput(["howdy add -y -U " + self.active_user]) + + dialog.destroy() + + if status != 1: + dialog = gtk.MessageDialog(parent=self, flags=gtk.DialogFlags.MODAL, type=gtk.MessageType.ERROR, buttons=gtk.ButtonsType.CLOSE) + dialog.props.text = "Error while adding model, error code " + str(status) + ": \n\n" + dialog.format_secondary_text(output) + dialog.set_title("Howdy Error") + dialog.run() + dialog.destroy() + + self.load_model_list() + + +def on_model_delete(self, select): + selection = self.treeview.get_selection() + (listmodel, rowlist) = selection.get_selected_rows() + + if len(rowlist) == 1: + id = listmodel.get_value(listmodel.get_iter(rowlist[0]), 0) + name = listmodel.get_value(listmodel.get_iter(rowlist[0]), 2) + + dialog = gtk.MessageDialog(parent=self, flags=gtk.DialogFlags.MODAL, buttons=gtk.ButtonsType.OK_CANCEL) + dialog.props.text = "Are you sure you want to delete model " + id + " (" + name + ")?" + dialog.set_title("Confirm Model Deletion") + response = dialog.run() + dialog.destroy() + + if response == gtk.ResponseType.OK: + status, output = subprocess.getstatusoutput(["howdy remove " + id + " -y -U " + self.active_user]) + + if status != 0: + dialog = gtk.MessageDialog(parent=self, flags=gtk.DialogFlags.MODAL, type=gtk.MessageType.ERROR, buttons=gtk.ButtonsType.CLOSE) + dialog.props.text = "Error while deleting model, error code " + str(status) + ": \n\n" + dialog.format_secondary_text(output) + dialog.set_title("Howdy Error") + dialog.run() + dialog.destroy() + + self.load_model_list() From 6e58ac99f9c8c63740b0ce952e734b25cdaaa165 Mon Sep 17 00:00:00 2001 From: boltgolt Date: Tue, 22 Dec 2020 17:44:52 +0100 Subject: [PATCH 07/13] Added about tab --- howdy-gtk/src/logo_about.png | Bin 0 -> 11081 bytes howdy-gtk/src/ui.glade | 118 +++++++++++++++++++++++++++++++++++ 2 files changed, 118 insertions(+) create mode 100644 howdy-gtk/src/logo_about.png diff --git a/howdy-gtk/src/logo_about.png b/howdy-gtk/src/logo_about.png new file mode 100644 index 0000000000000000000000000000000000000000..3d0c12e4c4712d459a6752dfdb33e3fcd7fb91b6 GIT binary patch literal 11081 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 + + + True + False + vertical + + + True + False + 40 + 20 + 12 + logo_about.png + + + False + True + 0 + + + + + True + False + Howdy + + + + + + False + True + 1 + + + + + True + False + 5 + Facial authentication for Linux + + + False + True + 2 + + + + + True + False + center + center + 15 + 35 + + + True + False + 3 + <a href="https://github.com/boltgolt/howdy">Open GitHub link</a> + True + False + + + + + + + + False + True + 1 + + + + + True + False + 3 + <a href="https://www.buymeacoffee.com/boltgolt">Donate to the project</a> + True + + + + + + + + False + True + 2 + + + + + False + True + 3 + + + + + 2 + + + + + True + False + About + + + 2 + False + + From adfb8a82d42137b79bada881f8da28961f432f67 Mon Sep 17 00:00:00 2001 From: boltgolt Date: Thu, 24 Dec 2020 14:16:27 +0100 Subject: [PATCH 08/13] Fixed links on about page --- howdy-gtk/src/ui.glade | 2 ++ howdy-gtk/src/window.py | 11 +++++++++++ 2 files changed, 13 insertions(+) diff --git a/howdy-gtk/src/ui.glade b/howdy-gtk/src/ui.glade index 8d9ae8b..778db5c 100644 --- a/howdy-gtk/src/ui.glade +++ b/howdy-gtk/src/ui.glade @@ -276,6 +276,7 @@ + False @@ -295,6 +296,7 @@ + False diff --git a/howdy-gtk/src/window.py b/howdy-gtk/src/window.py index d2607ff..010bff9 100644 --- a/howdy-gtk/src/window.py +++ b/howdy-gtk/src/window.py @@ -5,6 +5,7 @@ import signal import sys import os import elevate +import subprocess # Make sure we have the libs we need gi.require_version("Gtk", "3.0") @@ -81,6 +82,16 @@ class MainWindow(gtk.Window): self.treeview.set_model(self.listmodel) + def on_about_link(self, label, uri): + """Open links on about page as a non-root user""" + try: + user = os.getlogin() + except Exception: + user = os.environ.get("SUDO_USER") + + status, output = subprocess.getstatusoutput(["sudo -u " + user + " timeout 10 xdg-open " + uri]) + return True + def exit(self, widget, context): """Cleanly exit""" gtk.main_quit() From ac933f851e588ee172626af1bf45ff8922f13326 Mon Sep 17 00:00:00 2001 From: boltgolt Date: Sun, 3 Jan 2021 16:10:13 +0100 Subject: [PATCH 09/13] Added internalisation and user add button --- howdy-gtk/src/authsticky.py | 1 + howdy-gtk/src/i18n.py | 12 +++++ howdy-gtk/src/ui.glade | 1 + howdy-gtk/src/window.py | 32 ++++++++---- howdy-gtk/src/window_tab_models.py | 83 ++++++++++++++++++++++-------- 5 files changed, 98 insertions(+), 31 deletions(-) create mode 100644 howdy-gtk/src/i18n.py diff --git a/howdy-gtk/src/authsticky.py b/howdy-gtk/src/authsticky.py index 3f21d07..2c0c590 100644 --- a/howdy-gtk/src/authsticky.py +++ b/howdy-gtk/src/authsticky.py @@ -50,6 +50,7 @@ class StickyWindow(gtk.Window): self.connect("draw", self.draw) # Listen for a force close or click event and exit self.connect("destroy", self.exit) + self.connect("delete_event", self.exit) self.connect("button-press-event", self.exit) # Create a GDK drawing, restricts the window size diff --git a/howdy-gtk/src/i18n.py b/howdy-gtk/src/i18n.py new file mode 100644 index 0000000..2b3afb2 --- /dev/null +++ b/howdy-gtk/src/i18n.py @@ -0,0 +1,12 @@ +# Support file for translations + +# Import modules +import gettext +import os + +# Get the right translation based on locale, falling back to base if none found +translation = gettext.translation("gtk", localedir=os.path.join(os.path.dirname(__file__), "locales"), fallback=True) +translation.install() + +# Export translation function as _ +_ = translation.gettext diff --git a/howdy-gtk/src/ui.glade b/howdy-gtk/src/ui.glade index 778db5c..9f8cf9c 100644 --- a/howdy-gtk/src/ui.glade +++ b/howdy-gtk/src/ui.glade @@ -58,6 +58,7 @@ True True bottom + False diff --git a/howdy-gtk/src/window.py b/howdy-gtk/src/window.py index 010bff9..89e413e 100644 --- a/howdy-gtk/src/window.py +++ b/howdy-gtk/src/window.py @@ -7,6 +7,8 @@ import os import elevate import subprocess +from i18n import _ + # Make sure we have the libs we need gi.require_version("Gtk", "3.0") gi.require_version("Gdk", "3.0") @@ -23,6 +25,7 @@ class MainWindow(gtk.Window): gtk.Window.__init__(self) self.connect("destroy", self.exit) + self.connect("delete_event", self.exit) self.builder = gtk.Builder() self.builder.add_from_file("./ui.glade") @@ -34,9 +37,10 @@ class MainWindow(gtk.Window): # Create a treeview that will list the model data self.treeview = gtk.TreeView() + self.treeview.set_vexpand(True) # Set the coloums - for i, column in enumerate(["ID", "Created", "Label"]): + for i, column in enumerate([_("ID"), _("Created"), _("Label")]): cell = gtk.CellRendererText() col = gtk.TreeViewColumn(column, cell, text=i) @@ -48,8 +52,11 @@ class MainWindow(gtk.Window): filelist = os.listdir("/lib/security/howdy/models") self.active_user = "" + self.userlist.items = 0 + for file in filelist: self.userlist.append_text(file[:-4]) + self.userlist.items += 1 if not self.active_user: self.active_user = file[:-4] @@ -66,19 +73,22 @@ class MainWindow(gtk.Window): """(Re)load the model list""" # Execute the list commond to get the models - # output = subprocess.check_output(["howdy", "list", "--plain", "-U", self.active_user]) + # status, output = subprocess.getstatusoutput(["howdy list --plain -U " + self.active_user]) + status = 0 output = "1,2020-12-05 14:10:22,sd\n2,2020-12-05 14:22:41,\n3,2020-12-05 14:57:37,Model #3" + self.active_user - # Split the output per line - # lines = output.decode("utf-8").split("\n") - lines = output.split("\n") - # Create a datamodel self.listmodel = gtk.ListStore(str, str, str) - # Add the models to the datamodel - for i in range(len(lines)): - self.listmodel.append(lines[i].split(",")) + # If there was no error + if status == 0: + # Split the output per line + # lines = output.decode("utf-8").split("\n") + lines = output.split("\n") + + # Add the models to the datamodel + for i in range(len(lines)): + self.listmodel.append(lines[i].split(",")) self.treeview.set_model(self.listmodel) @@ -95,7 +105,7 @@ class MainWindow(gtk.Window): def exit(self, widget, context): """Cleanly exit""" gtk.main_quit() - sys.exit() + sys.exit(0) # Make sure we quit on a SIGINT @@ -104,7 +114,9 @@ signal.signal(signal.SIGINT, signal.SIG_DFL) # Make sure we run as sudo elevate.elevate() +# Class is split so it isn't too long, import split functions import window_tab_models +MainWindow.on_user_add = window_tab_models.on_user_add MainWindow.on_user_change = window_tab_models.on_user_change MainWindow.on_model_add = window_tab_models.on_model_add MainWindow.on_model_delete = window_tab_models.on_model_delete diff --git a/howdy-gtk/src/window_tab_models.py b/howdy-gtk/src/window_tab_models.py index a79ab8a..fe61619 100644 --- a/howdy-gtk/src/window_tab_models.py +++ b/howdy-gtk/src/window_tab_models.py @@ -1,5 +1,7 @@ import subprocess +import time +from i18n import _ from gi.repository import Gtk as gtk @@ -8,48 +10,87 @@ def on_user_change(self, select): self.load_model_list() -def on_model_add(self, select): +def on_user_add(self, button): + # Open question dialog dialog = gtk.MessageDialog(parent=self, flags=gtk.DialogFlags.MODAL, type=gtk.MessageType.QUESTION, buttons=gtk.ButtonsType.OK_CANCEL) - dialog.props.text = "Please enter a name for the new model, 24 characters max" - dialog.set_title("Confirm Model Creation") - # create the text input field + dialog.set_title(_("Confirm User Creation")) + dialog.props.text = _("Please enter the username of the user you want to add to Howdy") + + # Create the input field entry = gtk.Entry() - # create a horizontal box to pack the entry and a label + + # Add a label to ask for a model name hbox = gtk.HBox() - hbox.pack_start(gtk.Label("Model name:"), False, 5, 5) + hbox.pack_start(gtk.Label(_("Username:")), False, 5, 5) hbox.pack_end(entry, True, True, 5) - # some secondary text - # add it and show it + + # Add the box and show the dialog dialog.vbox.pack_end(hbox, True, True, 0) dialog.show_all() - # go go go + + # Show dialog response = dialog.run() - text = entry.get_text() + entered_user = entry.get_text() + dialog.destroy() + + if response == gtk.ResponseType.OK: + self.userlist.append_text(entered_user) + self.userlist.set_active(self.userlist.items) + self.userlist.items += 1 + + self.active_user = entered_user + self.load_model_list() + + +def on_model_add(self, button): + # Open question dialog + dialog = gtk.MessageDialog(parent=self, flags=gtk.DialogFlags.MODAL, type=gtk.MessageType.QUESTION, buttons=gtk.ButtonsType.OK_CANCEL) + dialog.set_title(_("Confirm Model Creation")) + dialog.props.text = _("Please enter a name for the new model, 24 characters max") + + # Create the input field + entry = gtk.Entry() + + # Add a label to ask for a model name + hbox = gtk.HBox() + hbox.pack_start(gtk.Label(_("Model name:")), False, 5, 5) + hbox.pack_end(entry, True, True, 5) + + # Add the box and show the dialog + dialog.vbox.pack_end(hbox, True, True, 0) + dialog.show_all() + + # Show dialog + response = dialog.run() + + entered_name = entry.get_text() dialog.destroy() if response == gtk.ResponseType.OK: dialog = gtk.MessageDialog(parent=self, flags=gtk.DialogFlags.MODAL) - dialog.props.text = "Please look directly into the camera" - dialog.set_title("Creating Model") + dialog.set_title(_("Creating Model")) + dialog.props.text = _("Please look directly into the camera") dialog.show_all() - status, output = subprocess.getstatusoutput(["howdy add -y -U " + self.active_user]) + time.sleep(1) + + status, output = subprocess.getstatusoutput(["howdy add -y -U " + self.active_user + " '" + entered_name + "'"]) dialog.destroy() - if status != 1: + if status != 0: dialog = gtk.MessageDialog(parent=self, flags=gtk.DialogFlags.MODAL, type=gtk.MessageType.ERROR, buttons=gtk.ButtonsType.CLOSE) - dialog.props.text = "Error while adding model, error code " + str(status) + ": \n\n" + dialog.set_title(_("Howdy Error")) + dialog.props.text = _("Error while adding model, error code {}: \n\n").format(str(status)) dialog.format_secondary_text(output) - dialog.set_title("Howdy Error") dialog.run() dialog.destroy() self.load_model_list() -def on_model_delete(self, select): +def on_model_delete(self, button): selection = self.treeview.get_selection() (listmodel, rowlist) = selection.get_selected_rows() @@ -58,8 +99,8 @@ def on_model_delete(self, select): name = listmodel.get_value(listmodel.get_iter(rowlist[0]), 2) dialog = gtk.MessageDialog(parent=self, flags=gtk.DialogFlags.MODAL, buttons=gtk.ButtonsType.OK_CANCEL) - dialog.props.text = "Are you sure you want to delete model " + id + " (" + name + ")?" - dialog.set_title("Confirm Model Deletion") + dialog.set_title(_("Confirm Model Deletion")) + dialog.props.text = _("Are you sure you want to delete model {id} ({name})?").format(id=id, name=name) response = dialog.run() dialog.destroy() @@ -68,9 +109,9 @@ def on_model_delete(self, select): if status != 0: dialog = gtk.MessageDialog(parent=self, flags=gtk.DialogFlags.MODAL, type=gtk.MessageType.ERROR, buttons=gtk.ButtonsType.CLOSE) - dialog.props.text = "Error while deleting model, error code " + str(status) + ": \n\n" + dialog.set_title(_("Howdy Error")) + dialog.props.text = _("Error while deleting model, error code {}: \n\n").format(status) dialog.format_secondary_text(output) - dialog.set_title("Howdy Error") dialog.run() dialog.destroy() From 4f3188cae546d84e79dd4b617e9ca35f78e47cde Mon Sep 17 00:00:00 2001 From: boltgolt Date: Mon, 4 Jan 2021 12:38:10 +0100 Subject: [PATCH 10/13] Tab models file rename --- howdy-gtk/src/{window_tab_models.py => tab_models.py} | 0 howdy-gtk/src/window.py | 10 +++++----- 2 files changed, 5 insertions(+), 5 deletions(-) rename howdy-gtk/src/{window_tab_models.py => tab_models.py} (100%) diff --git a/howdy-gtk/src/window_tab_models.py b/howdy-gtk/src/tab_models.py similarity index 100% rename from howdy-gtk/src/window_tab_models.py rename to howdy-gtk/src/tab_models.py diff --git a/howdy-gtk/src/window.py b/howdy-gtk/src/window.py index 89e413e..65ed655 100644 --- a/howdy-gtk/src/window.py +++ b/howdy-gtk/src/window.py @@ -115,11 +115,11 @@ signal.signal(signal.SIGINT, signal.SIG_DFL) elevate.elevate() # Class is split so it isn't too long, import split functions -import window_tab_models -MainWindow.on_user_add = window_tab_models.on_user_add -MainWindow.on_user_change = window_tab_models.on_user_change -MainWindow.on_model_add = window_tab_models.on_model_add -MainWindow.on_model_delete = window_tab_models.on_model_delete +import tab_models +MainWindow.on_user_add = tab_models.on_user_add +MainWindow.on_user_change = tab_models.on_user_change +MainWindow.on_model_add = tab_models.on_model_add +MainWindow.on_model_delete = tab_models.on_model_delete # Open the GTK window window = MainWindow() From 9f4c1a7fe6fad1f206e86d7894927139451f225b Mon Sep 17 00:00:00 2001 From: boltgolt Date: Mon, 4 Jan 2021 23:51:49 +0100 Subject: [PATCH 11/13] Added entire onboarding flow --- howdy-gtk/src/{ui.glade => main.glade} | 27 +- howdy-gtk/src/onboarding.glade | 470 +++++++++++++++++++++++++ howdy-gtk/src/onboarding.py | 256 ++++++++++++++ howdy-gtk/src/window.py | 13 +- src/dlib-data/install.sh | 1 + 5 files changed, 758 insertions(+), 9 deletions(-) rename howdy-gtk/src/{ui.glade => main.glade} (93%) create mode 100644 howdy-gtk/src/onboarding.glade create mode 100644 howdy-gtk/src/onboarding.py diff --git a/howdy-gtk/src/ui.glade b/howdy-gtk/src/main.glade similarity index 93% rename from howdy-gtk/src/ui.glade rename to howdy-gtk/src/main.glade index 9f8cf9c..8733770 100644 --- a/howdy-gtk/src/ui.glade +++ b/howdy-gtk/src/main.glade @@ -2,6 +2,21 @@ + + True + False + gtk-add + + + True + False + gtk-add + + + True + False + gtk-delete + False 5 @@ -57,7 +72,9 @@ True True True - bottom + iconadduser + none + True @@ -113,11 +130,11 @@ - gtk-add + Add True True True - True + iconadd 0.5899999737739563 True @@ -131,12 +148,12 @@ - gtk-delete + Delete True True True 5 - True + icondelete True diff --git a/howdy-gtk/src/onboarding.glade b/howdy-gtk/src/onboarding.glade new file mode 100644 index 0000000..e3b4b95 --- /dev/null +++ b/howdy-gtk/src/onboarding.glade @@ -0,0 +1,470 @@ + + + + + + True + False + 4 + gtk-cancel + + + True + False + 5 + gtk-apply + + + True + False + 4 + gtk-go-forward + + + True + False + 5 + gtk-media-play + + + 500 + 400 + False + Welcome to Howdy + center + logo.png + menu + center + + + True + False + immediate + vertical + + + False + True + vertical + + + True + False + 20 + Setup is done! + + + + + + False + True + 0 + + + + + True + False + 10 + 10 + 10 + 20 + We're done! Howdy is now active on this computer. Try doing anything you would normally have to type your password for to authenticate, like running a command with sudo. + +You can open the Howdy Configurator later on to change more advanced settings or add additional models. Press Finish below to close this window. + center + True + + + False + True + 1 + + + + + False + True + 0 + + + + + False + True + vertical + + + True + False + 20 + Adding a face model + + + + + + False + True + 0 + + + + + True + False + 10 + 10 + 10 + 20 + To authenticate you Howdy needs to save a model of your face to recognise you. Press the Scan button below to start the facial scan. + center + True + + + False + True + 1 + + + + + True + False + + + + + + Start face scan + True + True + True + True + True + 50 + 50 + iconscan + none + right + True + + + + True + True + 1 + + + + + + + + False + True + 2 + + + + + False + True + 1 + + + + + False + True + vertical + + + True + False + 20 + Configuring webcam + + + + + + False + True + 0 + + + + + True + False + 10 + 10 + 10 + 20 + Howdy will search your system automatically for any available cameras, so make sure your webcam is connected. After detection a list of usable webcams will be shown. Pick the one you want to use and click Next. + center + True + + + False + True + 1 + + + + + True + False + 0.89000000000000001 + 10 + 10 + vertical + + + True + False + 15 + Testing your webcams, please wait... + + + + + + False + True + 0 + + + + + + + + False + True + 2 + + + + + False + True + 3 + + + + + False + True + 10 + vertical + + + True + False + 20 + Downloading data files + + + + + + False + True + 0 + + + + + True + False + 10 + 10 + 10 + 20 + Howdy needs three pre trained facial recognition datasets to be able to recognise you, which will be downloaded now. You can see the download progress below. + center + True + + + False + True + 1 + + + + + True + False + + + True + False + 10 + 10 + 10 + Starting download... + center + + + + + + + + + True + True + 3 + + + + + True + True + 4 + + + + + True + False + vertical + + + False + 20 + 10 + 7 + 13 + logo_about.png + + + False + True + 0 + + + + + True + False + 5 + Welcome to Howdy! + + + + + + False + True + 1 + + + + + 100 + True + False + center + center + 20 + 20 + 10 + This wizard will walk you through the setup process and automatically configure Howdy for you. Press next to continue. + center + True + + + False + True + 2 + + + + + False + True + 6 + + + + + True + False + 10 + + + Cancel + True + True + True + 10 + iconcancel + True + + + + False + True + 0 + + + + + + + + Next + True + True + True + True + True + 10 + iconforward + none + right + True + + + + False + True + end + 2 + + + + + Finish setup + True + True + True + 10 + iconfinish + none + True + + + + False + True + end + 3 + + + + + False + True + end + 7 + + + + + + diff --git a/howdy-gtk/src/onboarding.py b/howdy-gtk/src/onboarding.py new file mode 100644 index 0000000..01340f5 --- /dev/null +++ b/howdy-gtk/src/onboarding.py @@ -0,0 +1,256 @@ +import sys +import os +import re +import time +import subprocess + +from i18n import _ + +from gi.repository import Gtk as gtk +from gi.repository import Gdk as gdk +from gi.repository import GObject as gobject +from gi.repository import Pango as pango + + +class OnboardingWindow(gtk.Window): + def __init__(self): + """Initialize the sticky window""" + print("create") + # Make the class a GTK window + gtk.Window.__init__(self) + + self.connect("destroy", self.exit) + self.connect("delete_event", self.exit) + + self.builder = gtk.Builder() + self.builder.add_from_file("./onboarding.glade") + self.builder.connect_signals(self) + + self.window = self.builder.get_object("onboardingwindow") + self.nextbutton = self.builder.get_object("nextbutton") + + self.slides = [ + self.builder.get_object("slide0"), + self.builder.get_object("slide1"), + self.builder.get_object("slide2"), + self.builder.get_object("slide3"), + self.builder.get_object("slide4") + ] + + self.window.show_all() + self.window.resize(500, 400) + + self.window.current_slide = 0 + + # Start GTK main loop + gtk.main() + + def go_next_slide(self, button=None): + self.nextbutton.set_sensitive(False) + + self.slides[self.window.current_slide].hide() + # self.window.current_slide += 1 + self.slides[self.window.current_slide + 1].show() + self.window.current_slide += 1 + + if self.window.current_slide == 1: + self.execute_slide1() + elif self.window.current_slide == 2: + gobject.timeout_add(10, self.execute_slide2) + elif self.window.current_slide == 3: + self.execute_slide3() + elif self.window.current_slide == 4: + self.execute_slide4() + + def execute_slide1(self): + self.downloadoutputlabel = self.builder.get_object("downloadoutputlabel") + eventbox = self.builder.get_object("downloadeventbox") + eventbox.modify_bg(gtk.StateType.NORMAL, gdk.Color(red=0, green=0, blue=0)) + + if os.path.exists("/lib/security/howdy/dlib-data/shape_predictor_5_face_landmarks.dat"): + self.downloadoutputlabel.set_text(_("Datafiles have already been downloaded!\nClick Next to continue")) + self.enable_next() + return + + self.proc = subprocess.Popen("./install.sh", stdout=subprocess.PIPE, stderr=subprocess.STDOUT, shell=True, cwd="/lib/security/howdy/dlib-data") + + self.download_lines = [] + self.read_download_line() + + def read_download_line(self): + line = self.proc.stdout.readline() + print(line) + self.download_lines.append(line.decode("utf-8")) + + if len(self.download_lines) > 10: + self.download_lines.pop(0) + + self.downloadoutputlabel.set_text(" ".join(self.download_lines)) + + if line: + gobject.timeout_add(10, self.read_download_line) + return + + # Wait for the process to finish and check the status code + if self.proc.wait(5) != 0: + self.show_error(_("Error while downloading datafiles"), " ".join(self.download_lines)) + + self.downloadoutputlabel.set_text(_("Done!\nClick Next to continue")) + self.enable_next() + + def execute_slide2(self): + def is_gray(frame): + for row in frame: + for pixel in row: + if not pixel[0] == pixel[1] == pixel[2]: + return False + return True + + try: + import cv2 + except Exception: + self.show_error(_("Error while importing OpenCV2"), _("Try reinstalling cv2")) + + device_ids = os.listdir("/dev/v4l/by-path") + device_rows = [] + + if not device_ids: + self.show_error(_("No webcams found on system"), _("Please configure your camera yourself if you are sure a compatible camera is connected")) + + # Loop though all devices + for dev in device_ids: + time.sleep(.5) + + # The full path to the device is the default name + device_path = "/dev/v4l/by-path/" + dev + device_name = dev + + # Get the udevadm details to try to get a better name + udevadm = subprocess.check_output(["udevadm info -r --query=all -n " + device_path], shell=True).decode("utf-8") + + # Loop though udevadm to search for a better name + for line in udevadm.split("\n"): + # Match it and encase it in quotes + re_name = re.search('product.*=(.*)$', line, re.IGNORECASE) + if re_name: + device_name = re_name.group(1) + + try: + capture = cv2.VideoCapture(device_path) + capture.grab() + ret, frame = capture.read() + except Exception: + device_rows.append([device_name, device_path, -9, _("No, camera can't be opened")]) + continue + + if not is_gray(frame): + device_rows.append([device_name, device_path, -5, _("No, not an infrared camera")]) + continue + + time.sleep(.2) + + ret, frame = capture.read() + + if not is_gray(frame): + device_rows.append([device_name, device_path, -5, _("No, not an infrared camera")]) + continue + + device_rows.append([device_name, device_path, 5, _("Yes, compatible infrared camera")]) + + capture.release() + + device_rows = sorted(device_rows, key=lambda k: -k[2]) + + self.loadinglabel = self.builder.get_object("loadinglabel") + self.devicelistbox = self.builder.get_object("devicelistbox") + + self.treeview = gtk.TreeView() + self.treeview.set_vexpand(True) + + # Set the coloums + for i, column in enumerate([_("Camera identifier or path"), _("Recommended")]): + cell = gtk.CellRendererText() + cell.set_property("ellipsize", pango.EllipsizeMode.END) + col = gtk.TreeViewColumn(column, cell, text=i) + self.treeview.append_column(col) + + # Add the treeview + self.devicelistbox.add(self.treeview) + + # Create a datamodel + self.listmodel = gtk.ListStore(str, str, str) + + for device in device_rows: + self.listmodel.append([device[0], device[3], device[1]]) + + self.treeview.set_model(self.listmodel) + self.treeview.set_cursor(0) + + self.loadinglabel.hide() + self.treeview.show() + self.enable_next() + + def execute_slide3(self): + selection = self.treeview.get_selection() + (listmodel, rowlist) = selection.get_selected_rows() + + if len(rowlist) != 1: + self.show_error(_("Error selecting camera")) + + device_path = listmodel.get_value(listmodel.get_iter(rowlist[0]), 2) + self.proc = subprocess.Popen("howdy set device_path " + device_path, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, shell=True) + + self.window.set_focus(self.builder.get_object("scanbutton")) + + def on_scanbutton_click(self, button): + status = self.proc.wait(2) + + if status != 0: + self.show_error(_("Error setting camera path"), _("Please set the camera path manually")) + + self.dialog = gtk.MessageDialog(parent=self, flags=gtk.DialogFlags.MODAL) + self.dialog.set_title(_("Creating Model")) + self.dialog.props.text = _("Please look directly into the camera") + self.dialog.show_all() + + # Wait a bit to allow the user to read the dialog + gobject.timeout_add(600, self.run_add) + + def run_add(self): + status, output = subprocess.getstatusoutput(["howdy add -y"]) + print(output) + + self.dialog.destroy() + + if status != 0: + self.show_error(_("Can't save face model"), output) + + gobject.timeout_add(10, self.go_next_slide) + + def execute_slide4(self): + self.nextbutton.hide() + self.builder.get_object("cancelbutton").hide() + + finishbutton = self.builder.get_object("finishbutton") + finishbutton.show() + self.window.set_focus(finishbutton) + + def enable_next(self): + self.nextbutton.set_sensitive(True) + self.window.set_focus(self.nextbutton) + + def show_error(self, error, secon=""): + dialog = gtk.MessageDialog(parent=self, flags=gtk.DialogFlags.MODAL, type=gtk.MessageType.ERROR, buttons=gtk.ButtonsType.CLOSE) + dialog.set_title(_("Howdy Error")) + dialog.props.text = error + dialog.format_secondary_text(secon) + + dialog.run() + + dialog.destroy() + self.exit() + + def exit(self, widget=None): + """Cleanly exit""" + gtk.main_quit() + sys.exit(0) diff --git a/howdy-gtk/src/window.py b/howdy-gtk/src/window.py index 65ed655..b92904a 100644 --- a/howdy-gtk/src/window.py +++ b/howdy-gtk/src/window.py @@ -28,7 +28,7 @@ class MainWindow(gtk.Window): self.connect("delete_event", self.exit) self.builder = gtk.Builder() - self.builder.add_from_file("./ui.glade") + self.builder.add_from_file("./main.glade") self.builder.connect_signals(self) self.window = self.builder.get_object("mainwindow") @@ -41,9 +41,7 @@ class MainWindow(gtk.Window): # Set the coloums for i, column in enumerate([_("ID"), _("Created"), _("Label")]): - cell = gtk.CellRendererText() - col = gtk.TreeViewColumn(column, cell, text=i) - + col = gtk.TreeViewColumn(column, gtk.CellRendererText(), text=i) self.treeview.append_column(col) # Add the treeview @@ -114,6 +112,13 @@ signal.signal(signal.SIGINT, signal.SIG_DFL) # Make sure we run as sudo elevate.elevate() +# If no models have been created yet, start the onboarding +if os.path.exists("/lib/security/howdy/models"): + import onboarding + onboarding.OnboardingWindow() + + sys.exit(0) + # Class is split so it isn't too long, import split functions import tab_models MainWindow.on_user_add = tab_models.on_user_add diff --git a/src/dlib-data/install.sh b/src/dlib-data/install.sh index 5698f01..f74a09f 100755 --- a/src/dlib-data/install.sh +++ b/src/dlib-data/install.sh @@ -21,5 +21,6 @@ else fi # Uncompress the data files and delete the original archive +echo " " echo "Unpacking..." bzip2 -d -f *.bz2 From 181318d20131954c030e481f9752d9f4bff6ce25 Mon Sep 17 00:00:00 2001 From: boltgolt Date: Fri, 8 Jan 2021 16:02:38 +0100 Subject: [PATCH 12/13] Onboarding is done --- howdy-gtk/src/onboarding.glade | 237 ++++++++++++++++++++++++++++++++- howdy-gtk/src/onboarding.py | 44 +++++- howdy-gtk/src/window.py | 4 +- 3 files changed, 272 insertions(+), 13 deletions(-) diff --git a/howdy-gtk/src/onboarding.glade b/howdy-gtk/src/onboarding.glade index e3b4b95..cd4dbe2 100644 --- a/howdy-gtk/src/onboarding.glade +++ b/howdy-gtk/src/onboarding.glade @@ -42,7 +42,7 @@ immediate vertical - + False True vertical @@ -72,7 +72,7 @@ 20 We're done! Howdy is now active on this computer. Try doing anything you would normally have to type your password for to authenticate, like running a command with sudo. -You can open the Howdy Configurator later on to change more advanced settings or add additional models. Press Finish below to close this window. +You can open the Howdy Configurator later on to change more advanced settings or add additional models. Press Finish below to close this window and open the Howdy Configurator. center True @@ -89,6 +89,233 @@ You can open the Howdy Configurator later on to change more advanced settings or 0 + + + False + True + vertical + + + True + False + 20 + Setting a certainty policy + + + + + + False + True + 0 + + + + + True + False + 10 + 10 + 10 + 40 + Because of changes in angles, distance, and other factors a face match is never exactly the same as the stored face model. On this page you can set how strict Howdy should be. + center + True + + + False + True + 1 + + + + + True + False + 60 + 60 + vertical + + + True + True + False + 10 + 0 + 0.50999999046325684 + True + True + radiobalanced + + + True + False + 5 + vertical + + + True + False + start + Fast + + + + + + False + True + 0 + + + + + True + False + start + Allows more fuzzy matches, +but speeds up the scanning process greatly. + True + + + False + True + 1 + + + + + + + False + True + 0 + + + + + True + True + False + 10 + 0 + True + True + + + True + False + 5 + vertical + + + True + False + start + Balanced + + + + + + False + True + 0 + + + + + True + False + start + Still relatively quick detection, +but might not log you in when further away. + True + + + False + True + 1 + + + + + + + False + True + 1 + + + + + True + True + False + 0 + True + True + radiobalanced + + + True + False + 5 + vertical + + + True + False + start + Secure + + + + + + False + False + 0 + + + + + True + False + start + The slightly safer option, +but will take much longer to authenticate you + True + + + False + False + 1 + + + + + + + False + True + 2 + + + + + False + True + 2 + + + + + False + True + 1 + + False @@ -171,7 +398,7 @@ You can open the Howdy Configurator later on to change more advanced settings or False True - 1 + 2 @@ -387,7 +614,7 @@ You can open the Howdy Configurator later on to change more advanced settings or False True - 6 + 5 @@ -461,7 +688,7 @@ You can open the Howdy Configurator later on to change more advanced settings or False True end - 7 + 8 diff --git a/howdy-gtk/src/onboarding.py b/howdy-gtk/src/onboarding.py index 01340f5..affa4fa 100644 --- a/howdy-gtk/src/onboarding.py +++ b/howdy-gtk/src/onboarding.py @@ -15,7 +15,6 @@ from gi.repository import Pango as pango class OnboardingWindow(gtk.Window): def __init__(self): """Initialize the sticky window""" - print("create") # Make the class a GTK window gtk.Window.__init__(self) @@ -34,7 +33,8 @@ class OnboardingWindow(gtk.Window): self.builder.get_object("slide1"), self.builder.get_object("slide2"), self.builder.get_object("slide3"), - self.builder.get_object("slide4") + self.builder.get_object("slide4"), + self.builder.get_object("slide5") ] self.window.show_all() @@ -49,7 +49,6 @@ class OnboardingWindow(gtk.Window): self.nextbutton.set_sensitive(False) self.slides[self.window.current_slide].hide() - # self.window.current_slide += 1 self.slides[self.window.current_slide + 1].show() self.window.current_slide += 1 @@ -61,6 +60,8 @@ class OnboardingWindow(gtk.Window): self.execute_slide3() elif self.window.current_slide == 4: self.execute_slide4() + elif self.window.current_slide == 5: + self.execute_slide5() def execute_slide1(self): self.downloadoutputlabel = self.builder.get_object("downloadoutputlabel") @@ -79,9 +80,11 @@ class OnboardingWindow(gtk.Window): def read_download_line(self): line = self.proc.stdout.readline() - print(line) self.download_lines.append(line.decode("utf-8")) + print("install.sh output:") + print(line.decode("utf-8")) + if len(self.download_lines) > 10: self.download_lines.pop(0) @@ -205,8 +208,8 @@ class OnboardingWindow(gtk.Window): def on_scanbutton_click(self, button): status = self.proc.wait(2) - if status != 0: - self.show_error(_("Error setting camera path"), _("Please set the camera path manually")) + # if status != 0: + # self.show_error(_("Error setting camera path"), _("Please set the camera path manually")) self.dialog = gtk.MessageDialog(parent=self, flags=gtk.DialogFlags.MODAL) self.dialog.set_title(_("Creating Model")) @@ -218,6 +221,8 @@ class OnboardingWindow(gtk.Window): def run_add(self): status, output = subprocess.getstatusoutput(["howdy add -y"]) + + print("howdy add output:") print(output) self.dialog.destroy() @@ -228,6 +233,28 @@ class OnboardingWindow(gtk.Window): gobject.timeout_add(10, self.go_next_slide) def execute_slide4(self): + self.enable_next() + + def execute_slide5(self): + radio_buttons = self.builder.get_object("radiobalanced").get_group() + radio_selected = False + radio_certanty = 5.0 + + for button in radio_buttons: + if button.get_active(): + radio_selected = gtk.Buildable.get_name(button) + + if not radio_selected: + self.show_error(_("Error reading radio buttons")) + elif radio_selected == "radiofast": + radio_certanty = 4.2 + elif radio_selected == "radiobalanced": + radio_certanty = 3.5 + elif radio_selected == "radiosecure": + radio_certanty = 2.2 + + self.proc = subprocess.Popen("howdy set certainty " + str(radio_certanty), stdout=subprocess.PIPE, stderr=subprocess.STDOUT, shell=True) + self.nextbutton.hide() self.builder.get_object("cancelbutton").hide() @@ -235,6 +262,11 @@ class OnboardingWindow(gtk.Window): finishbutton.show() self.window.set_focus(finishbutton) + status = self.proc.wait(2) + + if status != 0: + self.show_error(_("Error setting certainty"), _("Certainty is set to the default value, Howdy setup is complete")) + def enable_next(self): self.nextbutton.set_sensitive(True) self.window.set_focus(self.nextbutton) diff --git a/howdy-gtk/src/window.py b/howdy-gtk/src/window.py index b92904a..063adb1 100644 --- a/howdy-gtk/src/window.py +++ b/howdy-gtk/src/window.py @@ -112,8 +112,8 @@ signal.signal(signal.SIGINT, signal.SIG_DFL) # Make sure we run as sudo elevate.elevate() -# If no models have been created yet, start the onboarding -if os.path.exists("/lib/security/howdy/models"): +# If no models have been created yet or when it is forced, start the onboarding +if "--force-onboarding" in sys.argv or not os.path.exists("/lib/security/howdy/models"): import onboarding onboarding.OnboardingWindow() From db3ee86bc2518dfcfe8a8e20f9c94e146d5e5a9a Mon Sep 17 00:00:00 2001 From: boltgolt Date: Fri, 8 Jan 2021 18:49:58 +0100 Subject: [PATCH 13/13] Added video tab to window --- howdy-gtk/src/main.glade | 191 +++++++++++++++++++++++++++++++++---- howdy-gtk/src/tab_video.py | 79 +++++++++++++++ howdy-gtk/src/window.py | 13 ++- src/cli/test.py | 2 +- src/compare.py | 2 +- 5 files changed, 264 insertions(+), 23 deletions(-) create mode 100644 howdy-gtk/src/tab_video.py diff --git a/howdy-gtk/src/main.glade b/howdy-gtk/src/main.glade index 8733770..7cb12e7 100644 --- a/howdy-gtk/src/main.glade +++ b/howdy-gtk/src/main.glade @@ -5,16 +5,19 @@ True False + 5 gtk-add True False + 5 gtk-add True False + 5 gtk-delete @@ -24,10 +27,13 @@ center logo.png - + True True + 2 + left False + True @@ -37,9 +43,9 @@ True False - 5 - 5 - 5 + 10 + 10 + 10 5 @@ -72,6 +78,7 @@ True True True + 15 iconadduser none True @@ -95,15 +102,15 @@ True False - 5 - 5 + 10 + 10 vertical True False - 5 - 5 + 10 + 10 False @@ -122,9 +129,9 @@ True False - 5 - 7 - 5 + 10 + 8 + 10 @@ -152,7 +159,7 @@ True True True - 5 + 11 icondelete True @@ -178,7 +185,8 @@ True False - 2 + 10 + 10 Models @@ -190,21 +198,167 @@ True False - vertical + 10 + 10 + 10 + 10 - + True False - label + 10 + vertical + + + True + False + start + Camera ID: + + + + + + False + True + 0 + + + + + True + False + start + True + end + + + False + True + 1 + + + + + True + False + start + 10 + Real resolution: + + + + + + False + True + 2 + + + + + True + False + start + True + + + False + True + 3 + + + + + True + False + start + 10 + Used resolution: + + + + + + False + True + 4 + + + + + True + False + start + True + + + False + True + 5 + + + + + True + False + start + 10 + Recorder: + + + + + + False + True + 6 + + + + + True + False + start + True + + + False + True + 7 + + + + + False True + end 0 - + + 300 + 300 + True + False + + + True + False + gtk-execute + 6 + + + + + False + True + 1 + @@ -229,12 +383,13 @@ True False + center + center vertical True False - 40 20 12 logo_about.png diff --git a/howdy-gtk/src/tab_video.py b/howdy-gtk/src/tab_video.py new file mode 100644 index 0000000..cd67740 --- /dev/null +++ b/howdy-gtk/src/tab_video.py @@ -0,0 +1,79 @@ +import configparser + +from i18n import _ + +from gi.repository import Gtk as gtk +from gi.repository import Gdk as gdk +from gi.repository import GdkPixbuf as pixbuf +from gi.repository import GObject as gobject + +MAX_HEIGHT = 300 +MAX_WIDTH = 300 + + +def on_page_switch(self, notebook, page, page_num): + if page_num == 1: + path = "/dev/video1" + + try: + self.config = configparser.ConfigParser() + self.config.read("/lib/security/howdy/config.ini") + except Exception: + print(_("Can't open camera")) + + try: + # if not self.cv2: + import cv2 + self.cv2 = cv2 + except Exception: + print(_("Can't import OpenCV2")) + + try: + self.capture = cv2.VideoCapture(self.config.get("video", "device_path")) + except Exception: + print(_("Can't open camera")) + + opencvbox = self.builder.get_object("opencvbox") + opencvbox.modify_bg(gtk.StateType.NORMAL, gdk.Color(red=0, green=0, blue=0)) + + height = self.capture.get(self.cv2.CAP_PROP_FRAME_HEIGHT) or 1 + width = self.capture.get(self.cv2.CAP_PROP_FRAME_WIDTH) or 1 + + self.scaling_factor = (MAX_HEIGHT / height) or 1 + + if width * self.scaling_factor > MAX_WIDTH: + self.scaling_factor = (MAX_WIDTH / width) or 1 + + config_height = self.config.getfloat("video", "max_height", fallback=0.0) + config_scaling = (config_height / height) or 1 + + self.builder.get_object("videoid").set_text(path.split("/")[-1]) + self.builder.get_object("videores").set_text(str(int(width)) + "x" + str(int(height))) + self.builder.get_object("videoresused").set_text(str(int(width * config_scaling)) + "x" + str(int(height * config_scaling))) + self.builder.get_object("videorecorder").set_text(self.config.get("video", "recording_plugin", fallback=_("Unknown"))) + + gobject.timeout_add(10, self.capture_frame) + + elif self.capture is not None: + self.capture.release() + self.capture = None + + +def capture_frame(self): + if self.capture is None: + return + + ret, frame = self.capture.read() + + frame = self.cv2.resize(frame, None, fx=self.scaling_factor, fy=self.scaling_factor, interpolation=self.cv2.INTER_AREA) + + retval, buffer = self.cv2.imencode(".png", frame) + + loader = pixbuf.PixbufLoader() + loader.write(buffer) + loader.close() + buffer = loader.get_pixbuf() + + self.opencvimage.set_from_pixbuf(buffer) + + gobject.timeout_add(20, self.capture_frame) diff --git a/howdy-gtk/src/window.py b/howdy-gtk/src/window.py index 063adb1..1bd58ff 100644 --- a/howdy-gtk/src/window.py +++ b/howdy-gtk/src/window.py @@ -1,5 +1,4 @@ # Opens and controls main ui window -import cairo import gi import signal import sys @@ -15,7 +14,6 @@ gi.require_version("Gdk", "3.0") # Import them from gi.repository import Gtk as gtk -from gi.repository import Gdk as gdk class MainWindow(gtk.Window): @@ -34,6 +32,10 @@ class MainWindow(gtk.Window): self.window = self.builder.get_object("mainwindow") self.userlist = self.builder.get_object("userlist") self.modellistbox = self.builder.get_object("modellistbox") + self.opencvimage = self.builder.get_object("opencvimage") + + # Init capture for video tab + self.capture = None # Create a treeview that will list the model data self.treeview = gtk.TreeView() @@ -62,7 +64,6 @@ class MainWindow(gtk.Window): self.userlist.set_active(0) self.window.show_all() - # self.resize(300, 300) # Start GTK main loop gtk.main() @@ -102,6 +103,9 @@ class MainWindow(gtk.Window): def exit(self, widget, context): """Cleanly exit""" + if self.capture is not None: + self.capture.release() + gtk.main_quit() sys.exit(0) @@ -125,6 +129,9 @@ MainWindow.on_user_add = tab_models.on_user_add MainWindow.on_user_change = tab_models.on_user_change MainWindow.on_model_add = tab_models.on_model_add MainWindow.on_model_delete = tab_models.on_model_delete +import tab_video +MainWindow.on_page_switch = tab_video.on_page_switch +MainWindow.capture_frame = tab_video.capture_frame # Open the GTK window window = MainWindow() diff --git a/src/cli/test.py b/src/cli/test.py index c62fcef..37d5900 100644 --- a/src/cli/test.py +++ b/src/cli/test.py @@ -54,7 +54,7 @@ use_cnn = config.getboolean('core', 'use_cnn', fallback=False) if use_cnn: face_detector = dlib.cnn_face_detection_model_v1( - path + '/../dlib-data/mmod_human_face_detector.dat' + path + "/../dlib-data/mmod_human_face_detector.dat" ) else: face_detector = dlib.get_frontal_face_detector() diff --git a/src/compare.py b/src/compare.py index 770b75a..48a0172 100644 --- a/src/compare.py +++ b/src/compare.py @@ -29,7 +29,7 @@ def exit(code): global gtk_proc # Exit the auth ui process if there is one - if 'gtk_proc' in globals(): + if "gtk_proc" in globals(): gtk_proc.terminate() # Exit compare