0
0
Fork 0
mirror of https://github.com/boltgolt/howdy.git synced 2024-09-19 09:51:19 +02:00

Added entire onboarding flow

This commit is contained in:
boltgolt 2021-01-04 23:51:49 +01:00
parent 4f3188cae5
commit 9f4c1a7fe6
No known key found for this signature in database
GPG key ID: BECEC9937E1AAE26
5 changed files with 758 additions and 9 deletions

View file

@ -2,6 +2,21 @@
<!-- Generated with glade 3.18.3 -->
<interface domain="howdy">
<requires lib="gtk+" version="3.10"/>
<object class="GtkImage" id="iconadd">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="stock">gtk-add</property>
</object>
<object class="GtkImage" id="iconadduser">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="stock">gtk-add</property>
</object>
<object class="GtkImage" id="icondelete">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="stock">gtk-delete</property>
</object>
<object class="GtkWindow" id="mainwindow">
<property name="can_focus">False</property>
<property name="margin_top">5</property>
@ -57,7 +72,9 @@
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<property name="image_position">bottom</property>
<property name="image">iconadduser</property>
<property name="relief">none</property>
<property name="always_show_image">True</property>
<signal name="clicked" handler="on_user_add" swapped="no"/>
</object>
<packing>
@ -113,11 +130,11 @@
</child>
<child>
<object class="GtkButton" id="addbutton">
<property name="label">gtk-add</property>
<property name="label">Add</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<property name="use_stock">True</property>
<property name="image">iconadd</property>
<property name="xalign">0.5899999737739563</property>
<property name="always_show_image">True</property>
<signal name="clicked" handler="on_model_add" swapped="no"/>
@ -131,12 +148,12 @@
</child>
<child>
<object class="GtkButton" id="deletebutton">
<property name="label">gtk-delete</property>
<property name="label">Delete</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<property name="margin_right">5</property>
<property name="use_stock">True</property>
<property name="image">icondelete</property>
<property name="always_show_image">True</property>
<signal name="clicked" handler="on_model_delete" swapped="no"/>
</object>

View file

@ -0,0 +1,470 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- Generated with glade 3.18.3 -->
<interface>
<requires lib="gtk+" version="3.12"/>
<object class="GtkImage" id="iconcancel">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="margin_right">4</property>
<property name="stock">gtk-cancel</property>
</object>
<object class="GtkImage" id="iconfinish">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="margin_right">5</property>
<property name="stock">gtk-apply</property>
</object>
<object class="GtkImage" id="iconforward">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="margin_left">4</property>
<property name="stock">gtk-go-forward</property>
</object>
<object class="GtkImage" id="iconscan">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="margin_left">5</property>
<property name="stock">gtk-media-play</property>
</object>
<object class="GtkWindow" id="onboardingwindow">
<property name="width_request">500</property>
<property name="height_request">400</property>
<property name="can_focus">False</property>
<property name="title" translatable="yes">Welcome to Howdy</property>
<property name="window_position">center</property>
<property name="icon">logo.png</property>
<property name="type_hint">menu</property>
<property name="gravity">center</property>
<child>
<object class="GtkBox" id="slidecontainer">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="resize_mode">immediate</property>
<property name="orientation">vertical</property>
<child>
<object class="GtkBox" id="slide4">
<property name="can_focus">False</property>
<property name="no_show_all">True</property>
<property name="orientation">vertical</property>
<child>
<object class="GtkLabel" id="label9">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="margin_top">20</property>
<property name="label" translatable="yes">Setup is done!</property>
<attributes>
<attribute name="weight" value="bold"/>
</attributes>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="label10">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="margin_left">10</property>
<property name="margin_right">10</property>
<property name="margin_top">10</property>
<property name="margin_bottom">20</property>
<property name="label" translatable="yes">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. </property>
<property name="justify">center</property>
<property name="wrap">True</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkBox" id="slide3">
<property name="can_focus">False</property>
<property name="no_show_all">True</property>
<property name="orientation">vertical</property>
<child>
<object class="GtkLabel" id="label4">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="margin_top">20</property>
<property name="label" translatable="yes">Adding a face model</property>
<attributes>
<attribute name="weight" value="bold"/>
</attributes>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="label5">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="margin_left">10</property>
<property name="margin_right">10</property>
<property name="margin_top">10</property>
<property name="margin_bottom">20</property>
<property name="label" translatable="yes">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.</property>
<property name="justify">center</property>
<property name="wrap">True</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
<child>
<object class="GtkBox" id="box1">
<property name="visible">True</property>
<property name="can_focus">False</property>
<child>
<placeholder/>
</child>
<child>
<object class="GtkButton" id="scanbutton">
<property name="label" translatable="yes">Start face scan</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="has_focus">True</property>
<property name="is_focus">True</property>
<property name="receives_default">True</property>
<property name="margin_left">50</property>
<property name="margin_right">50</property>
<property name="image">iconscan</property>
<property name="relief">none</property>
<property name="image_position">right</property>
<property name="always_show_image">True</property>
<signal name="clicked" handler="on_scanbutton_click" swapped="no"/>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
<child>
<placeholder/>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">2</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
<child>
<object class="GtkBox" id="slide2">
<property name="can_focus">False</property>
<property name="no_show_all">True</property>
<property name="orientation">vertical</property>
<child>
<object class="GtkLabel" id="label2">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="margin_top">20</property>
<property name="label" translatable="yes">Configuring webcam</property>
<attributes>
<attribute name="weight" value="bold"/>
</attributes>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="label3">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="margin_left">10</property>
<property name="margin_right">10</property>
<property name="margin_top">10</property>
<property name="margin_bottom">20</property>
<property name="label" translatable="yes">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.</property>
<property name="justify">center</property>
<property name="wrap">True</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
<child>
<object class="GtkBox" id="devicelistbox">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="opacity">0.89000000000000001</property>
<property name="margin_left">10</property>
<property name="margin_right">10</property>
<property name="orientation">vertical</property>
<child>
<object class="GtkLabel" id="loadinglabel">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="margin_top">15</property>
<property name="label" translatable="yes">Testing your webcams, please wait...</property>
<attributes>
<attribute name="style" value="italic"/>
</attributes>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<placeholder/>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">2</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">3</property>
</packing>
</child>
<child>
<object class="GtkBox" id="slide1">
<property name="can_focus">False</property>
<property name="no_show_all">True</property>
<property name="margin_bottom">10</property>
<property name="orientation">vertical</property>
<child>
<object class="GtkLabel" id="label8">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="margin_top">20</property>
<property name="label" translatable="yes">Downloading data files</property>
<attributes>
<attribute name="weight" value="bold"/>
</attributes>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="label1">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="margin_left">10</property>
<property name="margin_right">10</property>
<property name="margin_top">10</property>
<property name="margin_bottom">20</property>
<property name="label" translatable="yes">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.</property>
<property name="justify">center</property>
<property name="wrap">True</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
<child>
<object class="GtkEventBox" id="downloadeventbox">
<property name="visible">True</property>
<property name="can_focus">False</property>
<child>
<object class="GtkLabel" id="downloadoutputlabel">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="margin_left">10</property>
<property name="margin_right">10</property>
<property name="margin_top">10</property>
<property name="label" translatable="yes">Starting download...</property>
<property name="justify">center</property>
<attributes>
<attribute name="foreground" value="#f3f3f3f3f3f3"/>
<attribute name="background" value="#000000000000"/>
</attributes>
</object>
</child>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">3</property>
</packing>
</child>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">4</property>
</packing>
</child>
<child>
<object class="GtkBox" id="slide0">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="orientation">vertical</property>
<child>
<object class="GtkImage" id="image2">
<property name="can_focus">False</property>
<property name="margin_top">20</property>
<property name="margin_bottom">10</property>
<property name="xpad">7</property>
<property name="ypad">13</property>
<property name="pixbuf">logo_about.png</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="label6">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="margin_top">5</property>
<property name="label" translatable="yes">Welcome to Howdy!</property>
<attributes>
<attribute name="scale" value="2"/>
</attributes>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="label7">
<property name="width_request">100</property>
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="halign">center</property>
<property name="valign">center</property>
<property name="margin_left">20</property>
<property name="margin_right">20</property>
<property name="margin_top">10</property>
<property name="label" translatable="yes">This wizard will walk you through the setup process and automatically configure Howdy for you. Press next to continue.</property>
<property name="justify">center</property>
<property name="wrap">True</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">2</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">6</property>
</packing>
</child>
<child>
<object class="GtkBox" id="navigationbar">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="margin_bottom">10</property>
<child>
<object class="GtkButton" id="cancelbutton">
<property name="label" translatable="yes">Cancel</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<property name="margin_left">10</property>
<property name="image">iconcancel</property>
<property name="always_show_image">True</property>
<signal name="clicked" handler="exit" swapped="no"/>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<placeholder/>
</child>
<child>
<object class="GtkButton" id="nextbutton">
<property name="label" translatable="yes">Next</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="has_focus">True</property>
<property name="is_focus">True</property>
<property name="receives_default">True</property>
<property name="margin_right">10</property>
<property name="image">iconforward</property>
<property name="relief">none</property>
<property name="image_position">right</property>
<property name="always_show_image">True</property>
<signal name="clicked" handler="go_next_slide" swapped="no"/>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="pack_type">end</property>
<property name="position">2</property>
</packing>
</child>
<child>
<object class="GtkButton" id="finishbutton">
<property name="label" translatable="yes">Finish setup</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<property name="no_show_all">True</property>
<property name="margin_right">10</property>
<property name="image">iconfinish</property>
<property name="relief">none</property>
<property name="always_show_image">True</property>
<signal name="clicked" handler="exit" swapped="no"/>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="pack_type">end</property>
<property name="position">3</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="pack_type">end</property>
<property name="position">7</property>
</packing>
</child>
</object>
</child>
</object>
</interface>

256
howdy-gtk/src/onboarding.py Normal file
View file

@ -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)

View file

@ -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

View file

@ -21,5 +21,6 @@ else
fi
# Uncompress the data files and delete the original archive
echo " "
echo "Unpacking..."
bzip2 -d -f *.bz2