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

Merge branch 'gkt-ui' into dev

This commit is contained in:
boltgolt 2021-01-08 19:01:31 +01:00
commit 2c14735ac4
No known key found for this signature in database
GPG key ID: BECEC9937E1AAE26
26 changed files with 2133 additions and 11 deletions

2
debian/control vendored
View file

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

View file

@ -0,0 +1,5 @@
howdy-gtk (0.0.1) xenial; urgency=medium
* Initial testing release with sticky authentication ui
-- boltgolt <boltgolt@gmail.com> Thu, 03 Dec 2020 00:08:49 +0200

1
howdy-gtk/debian/compat Normal file
View file

@ -0,0 +1 @@
10

13
howdy-gtk/debian/control Normal file
View file

@ -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 <boltgolt@gmail.com>
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, python-gtk2-dev, cmake
Description: Optional UI package for Howdy, written in Gtk

View file

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

View file

@ -0,0 +1 @@
/usr/lib/howdy-gtk/init.py /usr/bin/howdy-gtk

View file

@ -0,0 +1,2 @@
# W: Don't require ugly linebreaks in last 5 chars
howdy: debian-changelog-line-too-long

1
howdy-gtk/debian/install Normal file
View file

@ -0,0 +1 @@
src/. /usr/lib/howdy-gtk

View file

@ -0,0 +1,2 @@
#!/bin/sh
pip3 install elevate

8
howdy-gtk/debian/rules Executable file
View file

@ -0,0 +1,8 @@
#!/usr/bin/make -f
DH_VERBOSE = 1
DPKG_EXPORT_BUILDFLAGS = 1
include /usr/share/dpkg/default.mk
%:
dh $@

View file

@ -0,0 +1 @@
3.0 (native)

View file

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

157
howdy-gtk/src/authsticky.py Normal file
View file

@ -0,0 +1,157 @@
# 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
# Set default messages to show in the popup
message = "Starting up... "
subtext = ""
class StickyWindow(gtk.Window):
def __init__(self):
"""Initialize the sticky window"""
# Make the class a GTK window
gtk.Window.__init__(self)
# 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)
# 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("delete_event", self.exit)
self.connect("button-press-event", self.exit)
# Create a GDK drawing, restricts the window size
darea = gtk.DrawingArea()
darea.set_size_request(windowWidth, windowHeight)
self.add(darea)
# Get the default screen
screen = gdk.Screen.get_default()
visual = screen.get_rgba_visual()
if visual and screen.is_composited():
self.set_visual(visual)
# 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)
# Show window and force a resize again
self.show_all()
self.resize(windowWidth, windowHeight)
# Add a timeout to catch input passed from compare.py
gobject.timeout_add(100, self.catch_stdin)
# 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()

12
howdy-gtk/src/i18n.py Normal file
View file

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

8
howdy-gtk/src/init.py Executable file
View file

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

BIN
howdy-gtk/src/logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

512
howdy-gtk/src/main.glade Normal file
View file

@ -0,0 +1,512 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- 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="margin_right">5</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="margin_right">5</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="margin_right">5</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>
<property name="title" translatable="yes" context="Window title">Howdy Configuration</property>
<property name="window_position">center</property>
<property name="icon">logo.png</property>
<child>
<object class="GtkNotebook" id="notebook">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="margin_left">2</property>
<property name="tab_pos">left</property>
<property name="show_border">False</property>
<signal name="switch-page" handler="on_page_switch" swapped="no"/>
<child>
<object class="GtkBox" id="box3">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="orientation">vertical</property>
<child>
<object class="GtkBox" id="box4">
<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="spacing">5</property>
<child>
<object class="GtkLabel" id="userlabel">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="valign">center</property>
<property name="label" translatable="yes">Showing saved models for:</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkComboBoxText" id="userlist">
<property name="visible">True</property>
<property name="can_focus">False</property>
<signal name="changed" handler="on_user_change" swapped="no"/>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="position">1</property>
</packing>
</child>
<child>
<object class="GtkButton" id="adduserbutton">
<property name="label" translatable="yes">Add new user</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<property name="margin_left">15</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>
<property name="expand">False</property>
<property name="fill">False</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="position">0</property>
</packing>
</child>
<child>
<object class="GtkBox" id="modellistbox">
<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="orientation">vertical</property>
<child>
<object class="GtkSeparator" id="separator1">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="margin_top">10</property>
<property name="margin_bottom">10</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</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="box1">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="margin_right">10</property>
<property name="margin_top">8</property>
<property name="margin_bottom">10</property>
<child>
<placeholder/>
</child>
<child>
<object class="GtkButton" id="addbutton">
<property name="label">Add</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">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"/>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="pack_type">end</property>
<property name="position">1</property>
</packing>
</child>
<child>
<object class="GtkButton" id="deletebutton">
<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">11</property>
<property name="image">icondelete</property>
<property name="always_show_image">True</property>
<signal name="clicked" handler="on_model_delete" 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>
</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>
</object>
</child>
<child type="tab">
<object class="GtkLabel" id="modelstab">
<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="label" translatable="yes">Models</property>
</object>
<packing>
<property name="position">1</property>
<property name="tab_fill">False</property>
</packing>
</child>
<child>
<object class="GtkBox" id="box2">
<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">10</property>
<child>
<object class="GtkBox" id="box7">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="margin_left">10</property>
<property name="orientation">vertical</property>
<child>
<object class="GtkLabel" id="label1">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="halign">start</property>
<property name="label" translatable="yes">Camera ID:</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="videoid">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="halign">start</property>
<property name="selectable">True</property>
<property name="ellipsize">end</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="label6">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="halign">start</property>
<property name="margin_top">10</property>
<property name="label" translatable="yes">Real resolution:</property>
<attributes>
<attribute name="weight" value="bold"/>
</attributes>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">2</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="videores">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="halign">start</property>
<property name="selectable">True</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">3</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="label7">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="halign">start</property>
<property name="margin_top">10</property>
<property name="label" translatable="yes">Used resolution:</property>
<attributes>
<attribute name="weight" value="bold"/>
</attributes>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">4</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="videoresused">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="halign">start</property>
<property name="selectable">True</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">5</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="label8">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="halign">start</property>
<property name="margin_top">10</property>
<property name="label" translatable="yes">Recorder:</property>
<attributes>
<attribute name="weight" value="bold"/>
</attributes>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">6</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="videorecorder">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="halign">start</property>
<property name="selectable">True</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">7</property>
</packing>
</child>
<child>
<placeholder/>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="pack_type">end</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkEventBox" id="opencvbox">
<property name="width_request">300</property>
<property name="height_request">300</property>
<property name="visible">True</property>
<property name="can_focus">False</property>
<child>
<object class="GtkImage" id="opencvimage">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="stock">gtk-execute</property>
<property name="icon_size">6</property>
</object>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
<child>
<placeholder/>
</child>
</object>
<packing>
<property name="position">1</property>
</packing>
</child>
<child type="tab">
<object class="GtkLabel" id="videotab">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">Video</property>
</object>
<packing>
<property name="position">1</property>
<property name="tab_fill">False</property>
</packing>
</child>
<child>
<object class="GtkBox" id="box5">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="halign">center</property>
<property name="valign">center</property>
<property name="orientation">vertical</property>
<child>
<object class="GtkImage" id="image1">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="margin_bottom">20</property>
<property name="xpad">12</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="label2">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">Howdy</property>
<attributes>
<attribute name="weight" value="bold"/>
</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="label3">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="margin_top">5</property>
<property name="label" translatable="yes">Facial authentication for Linux</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">2</property>
</packing>
</child>
<child>
<object class="GtkBox" id="box6">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="halign">center</property>
<property name="valign">center</property>
<property name="margin_top">15</property>
<property name="margin_bottom">35</property>
<child>
<object class="GtkLabel" id="label4">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="margin_right">3</property>
<property name="label" translatable="yes">&lt;a href="https://github.com/boltgolt/howdy"&gt;Open GitHub link&lt;/a&gt; </property>
<property name="use_markup">True</property>
<property name="track_visited_links">False</property>
<attributes>
<attribute name="underline" value="True"/>
<attribute name="foreground" value="#2929ababe2e2"/>
<attribute name="underline-color" value="#2929ababe2e2"/>
</attributes>
<signal name="activate-link" handler="on_about_link" swapped="no"/>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="label5">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="margin_left">3</property>
<property name="label" translatable="yes">&lt;a href="https://www.buymeacoffee.com/boltgolt"&gt;Donate to the project&lt;/a&gt; </property>
<property name="use_markup">True</property>
<attributes>
<attribute name="underline" value="True"/>
<attribute name="foreground" value="#2929ababe2e2"/>
<attribute name="underline-color" value="#2929ababe2e2"/>
</attributes>
<signal name="activate-link" handler="on_about_link" swapped="no"/>
</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>
</object>
<packing>
<property name="position">2</property>
</packing>
</child>
<child type="tab">
<object class="GtkLabel" id="abouttab">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">About</property>
</object>
<packing>
<property name="position">2</property>
<property name="tab_fill">False</property>
</packing>
</child>
<child>
<placeholder/>
</child>
<child type="tab">
<placeholder/>
</child>
</object>
</child>
</object>
</interface>

View file

@ -0,0 +1,697 @@
<?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="slide5">
<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 and open the Howdy Configurator. </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="slide4">
<property name="can_focus">False</property>
<property name="no_show_all">True</property>
<property name="orientation">vertical</property>
<child>
<object class="GtkLabel" id="label11">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="margin_top">20</property>
<property name="label" translatable="yes">Setting a certainty policy</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="label12">
<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">40</property>
<property name="label" translatable="yes">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.</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="box2">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="margin_left">60</property>
<property name="margin_right">60</property>
<property name="orientation">vertical</property>
<child>
<object class="GtkRadioButton" id="radiofast">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">False</property>
<property name="margin_bottom">10</property>
<property name="xalign">0</property>
<property name="yalign">0.50999999046325684</property>
<property name="active">True</property>
<property name="draw_indicator">True</property>
<property name="group">radiobalanced</property>
<child>
<object class="GtkBox" id="box3">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="margin_left">5</property>
<property name="orientation">vertical</property>
<child>
<object class="GtkLabel" id="label13">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="halign">start</property>
<property name="label" translatable="yes">Fast</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="label14">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="halign">start</property>
<property name="label" translatable="yes">Allows more fuzzy matches,
but speeds up the scanning process greatly.</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>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkRadioButton" id="radiobalanced">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">False</property>
<property name="margin_bottom">10</property>
<property name="xalign">0</property>
<property name="active">True</property>
<property name="draw_indicator">True</property>
<child>
<object class="GtkBox" id="box4">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="margin_left">5</property>
<property name="orientation">vertical</property>
<child>
<object class="GtkLabel" id="label15">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="halign">start</property>
<property name="label" translatable="yes">Balanced</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="label16">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="halign">start</property>
<property name="label" translatable="yes">Still relatively quick detection,
but might not log you in when further away.</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>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
<child>
<object class="GtkRadioButton" id="radiosecure">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">False</property>
<property name="xalign">0</property>
<property name="active">True</property>
<property name="draw_indicator">True</property>
<property name="group">radiobalanced</property>
<child>
<object class="GtkBox" id="box5">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="margin_left">5</property>
<property name="orientation">vertical</property>
<child>
<object class="GtkLabel" id="label17">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="halign">start</property>
<property name="label" translatable="yes">Secure</property>
<attributes>
<attribute name="weight" value="bold"/>
</attributes>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="label18">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="halign">start</property>
<property name="label" translatable="yes">The slightly safer option,
but will take much longer to authenticate you</property>
<property name="wrap">True</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="position">1</property>
</packing>
</child>
</object>
</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">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="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">2</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">5</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">8</property>
</packing>
</child>
</object>
</child>
</object>
</interface>

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

@ -0,0 +1,288 @@
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"""
# 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.builder.get_object("slide5")
]
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.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()
elif self.window.current_slide == 5:
self.execute_slide5()
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()
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)
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("howdy add output:")
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.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()
finishbutton = self.builder.get_object("finishbutton")
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)
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)

118
howdy-gtk/src/tab_models.py Normal file
View file

@ -0,0 +1,118 @@
import subprocess
import time
from i18n import _
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_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.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()
# Add a label to ask for a model name
hbox = gtk.HBox()
hbox.pack_start(gtk.Label(_("Username:")), 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_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.set_title(_("Creating Model"))
dialog.props.text = _("Please look directly into the camera")
dialog.show_all()
time.sleep(1)
status, output = subprocess.getstatusoutput(["howdy add -y -U " + self.active_user + " '" + entered_name + "'"])
dialog.destroy()
if status != 0:
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 while adding model, error code {}: \n\n").format(str(status))
dialog.format_secondary_text(output)
dialog.run()
dialog.destroy()
self.load_model_list()
def on_model_delete(self, button):
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.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()
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.set_title(_("Howdy Error"))
dialog.props.text = _("Error while deleting model, error code {}: \n\n").format(status)
dialog.format_secondary_text(output)
dialog.run()
dialog.destroy()
self.load_model_list()

View file

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

137
howdy-gtk/src/window.py Normal file
View file

@ -0,0 +1,137 @@
# Opens and controls main ui window
import gi
import signal
import sys
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")
# Import them
from gi.repository import Gtk as gtk
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.connect("delete_event", self.exit)
self.builder = gtk.Builder()
self.builder.add_from_file("./main.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")
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()
self.treeview.set_vexpand(True)
# Set the coloums
for i, column in enumerate([_("ID"), _("Created"), _("Label")]):
col = gtk.TreeViewColumn(column, gtk.CellRendererText(), 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 = ""
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]
self.userlist.set_active(0)
self.window.show_all()
# Start GTK main loop
gtk.main()
def load_model_list(self):
"""(Re)load the model list"""
# Execute the list commond to get the models
# 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
# Create a datamodel
self.listmodel = gtk.ListStore(str, str, str)
# 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)
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"""
if self.capture is not None:
self.capture.release()
gtk.main_quit()
sys.exit(0)
# Make sure we quit on a SIGINT
signal.signal(signal.SIGINT, signal.SIG_DFL)
# Make sure we run as sudo
elevate.elevate()
# 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()
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
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()

View file

@ -55,7 +55,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()

View file

@ -17,6 +17,7 @@ import configparser
import dlib
import cv2
import datetime
import subprocess
import snapshot
import numpy as np
import _thread as thread
@ -25,6 +26,18 @@ from recorders.video_capture import VideoCapture
from i18n import _
def exit(code):
"""Exit while closeing howdy-gtk properly"""
global gtk_proc
# Exit the auth ui process if there is one
if "gtk_proc" in globals():
gtk_proc.terminate()
# Exit compare
sys.exit(code)
def init_detector(lock):
"""Start face detector, encoder and predictor in a new thread"""
global face_detector, pose_predictor, face_encoder
@ -35,7 +48,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:
@ -64,9 +77,26 @@ def make_snapshot(type):
])
def send_to_ui(type, message):
"""Send message to the auth ui"""
global gtk_proc
# 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
# 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__ + "/..")
@ -92,6 +122,15 @@ face_detector = None
pose_predictor = None
face_encoder = None
# Start the auth ui
try:
gtk_proc = subprocess.Popen(["howdy-gtk", "--start-auth-ui"], 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...")
# Try to load the face model from the models folder
try:
models = json.load(open(PATH + "/models/" + user + ".dat"))
@ -99,11 +138,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()
@ -158,18 +197,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 - dark_tries) + " 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
@ -179,9 +229,9 @@ while True:
if dark_tries == valid_frames:
print(_("All frames were too dark, please check dark_threshold in config"))
print(_("Average darkness: {avg}, Threshold: {threshold}").format(avg=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()
@ -295,7 +345,7 @@ while True:
})
# End peacefully
sys.exit(0)
exit(0)
if exposure != -1:
# For a strange reason on some cameras (e.g. Lenoxo X1E)

View file

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