mirror of
https://github.com/boltgolt/howdy.git
synced 2024-09-12 09:41:18 +02:00
Merge pull request #803 from musikid/meson
build: add meson build system
This commit is contained in:
commit
95df4d2d48
39 changed files with 530 additions and 135 deletions
7
.clang-tidy
Normal file
7
.clang-tidy
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
Checks: 'clang-diagnostic-*,clang-analyser-*,-clang-diagnostic-unused-command-line-argument,google-*,bugprone-*,modernize-*,performance-*,portability-*,readability-*,-bugprone-easily-swappable-*,-readability-magic-numbers,-google-readability-todo'
|
||||||
|
WarningsAsErrors: 'clang-diagnostic-*,clang-analyser-*,-clang-diagnostic-unused-command-line-argument,google-*,bugprone-*,modernize-*,performance-*,portability-*,readability-*,-bugprone-easily-swappable-*,-readability-magic-numbers,-google-readability-todo'
|
||||||
|
CheckOptions:
|
||||||
|
- key: readability-function-cognitive-complexity.Threshold
|
||||||
|
value: '50'
|
||||||
|
|
||||||
|
# vim:syntax=yaml
|
7
.github/workflows/check.yml
vendored
7
.github/workflows/check.yml
vendored
|
@ -8,16 +8,19 @@ jobs:
|
||||||
- name: Install required libraries
|
- name: Install required libraries
|
||||||
run: >
|
run: >
|
||||||
sudo apt-get update && sudo apt-get install -y
|
sudo apt-get update && sudo apt-get install -y
|
||||||
python3 python3-pip python3-setuptools python3-wheel ninja-build meson
|
python3 python3-pip python3-setuptools python3-wheel
|
||||||
cmake make build-essential clang-tidy
|
cmake make build-essential clang-tidy
|
||||||
libpam0g-dev libinih-dev libevdev-dev
|
libpam0g-dev libinih-dev libevdev-dev
|
||||||
python3-dev libopencv-dev
|
python3-dev libopencv-dev
|
||||||
|
|
||||||
|
- name: Install meson
|
||||||
|
run: sudo python3 -m pip install meson ninja
|
||||||
|
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v2
|
||||||
|
|
||||||
- name: Build
|
- name: Build
|
||||||
run: |
|
run: |
|
||||||
meson setup build howdy/src/pam
|
meson setup build
|
||||||
ninja -C build
|
ninja -C build
|
||||||
|
|
||||||
- name: Check source code
|
- name: Check source code
|
||||||
|
|
3
howdy-gtk/bin/howdy-gtk.in
Normal file
3
howdy-gtk/bin/howdy-gtk.in
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
env python3 "@script_path@" "$@"
|
82
howdy-gtk/meson.build
Normal file
82
howdy-gtk/meson.build
Normal file
|
@ -0,0 +1,82 @@
|
||||||
|
if meson.is_subproject()
|
||||||
|
project('howdy-gtk', license: 'MIT', version: 'beta', meson_version: '>= 0.64.0')
|
||||||
|
endif
|
||||||
|
|
||||||
|
datadir = get_option('prefix') / get_option('datadir') / 'howdy-gtk'
|
||||||
|
py_conf = configuration_data(paths_dict)
|
||||||
|
py_conf.set('data_dir', datadir)
|
||||||
|
|
||||||
|
|
||||||
|
py_paths = configure_file(
|
||||||
|
input: 'src/paths.py.in',
|
||||||
|
output: 'paths.py',
|
||||||
|
configuration: py_conf,
|
||||||
|
)
|
||||||
|
|
||||||
|
sources = files(
|
||||||
|
'src/authsticky.py',
|
||||||
|
'src/i18n.py',
|
||||||
|
'src/init.py',
|
||||||
|
'src/onboarding.py',
|
||||||
|
'src/paths_factory.py',
|
||||||
|
'src/tab_models.py',
|
||||||
|
'src/tab_video.py',
|
||||||
|
'src/window.py',
|
||||||
|
)
|
||||||
|
|
||||||
|
py = import('python').find_installation(
|
||||||
|
# modules: ['gi', 'elevate']
|
||||||
|
)
|
||||||
|
py.dependency()
|
||||||
|
|
||||||
|
if get_option('install_in_site_packages')
|
||||||
|
pysourcesinstalldir = join_paths(py.get_install_dir(), 'howdy-gtk')
|
||||||
|
else
|
||||||
|
pysourcesinstalldir = get_option('py_sources_dir') != '' ? get_option('py_sources_dir') : join_paths(get_option('prefix'), get_option('libdir'), 'howdy-gtk')
|
||||||
|
endif
|
||||||
|
|
||||||
|
if get_option('install_in_site_packages')
|
||||||
|
py.install_sources(
|
||||||
|
sources,
|
||||||
|
py_paths,
|
||||||
|
subdir: 'howdy-gtk',
|
||||||
|
install_mode: 'r--r--r--',
|
||||||
|
install_tag: 'py_sources',
|
||||||
|
)
|
||||||
|
else
|
||||||
|
install_data(
|
||||||
|
sources,
|
||||||
|
py_paths,
|
||||||
|
install_dir: pysourcesinstalldir,
|
||||||
|
install_mode: 'r--r--r--',
|
||||||
|
install_tag: 'py_sources',
|
||||||
|
)
|
||||||
|
endif
|
||||||
|
|
||||||
|
logos = files(
|
||||||
|
'src/logo.png',
|
||||||
|
'src/logo_about.png',
|
||||||
|
)
|
||||||
|
install_data(logos, install_dir: datadir)
|
||||||
|
|
||||||
|
interface_files = files(
|
||||||
|
'src/main.glade',
|
||||||
|
'src/onboarding.glade',
|
||||||
|
)
|
||||||
|
install_data(interface_files, install_dir: datadir)
|
||||||
|
|
||||||
|
cli_path = join_paths(pysourcesinstalldir, 'init.py')
|
||||||
|
conf_data = configuration_data({ 'script_path': cli_path })
|
||||||
|
|
||||||
|
bin_name = 'howdy-gtk'
|
||||||
|
bin = configure_file(
|
||||||
|
input: 'bin/howdy-gtk.in',
|
||||||
|
output: bin_name,
|
||||||
|
configuration: conf_data
|
||||||
|
)
|
||||||
|
install_data(
|
||||||
|
bin,
|
||||||
|
install_mode: 'rwxr-xr-x',
|
||||||
|
install_dir: get_option('prefix') / get_option('bindir'),
|
||||||
|
install_tag: 'bin',
|
||||||
|
)
|
|
@ -3,6 +3,7 @@ import cairo
|
||||||
import gi
|
import gi
|
||||||
import signal
|
import signal
|
||||||
import sys
|
import sys
|
||||||
|
import paths_factory
|
||||||
import os
|
import os
|
||||||
|
|
||||||
from i18n import _
|
from i18n import _
|
||||||
|
@ -32,9 +33,7 @@ class StickyWindow(gtk.Window):
|
||||||
gtk.Window.__init__(self)
|
gtk.Window.__init__(self)
|
||||||
|
|
||||||
# Get the absolute or relative path to the logo file
|
# Get the absolute or relative path to the logo file
|
||||||
logo_path = "/usr/lib/howdy-gtk/logo.png"
|
logo_path = paths_factory.logo_path()
|
||||||
if not os.access(logo_path, os.R_OK):
|
|
||||||
logo_path = "./logo.png"
|
|
||||||
|
|
||||||
# Create image and calculate scale size based on image size
|
# Create image and calculate scale size based on image size
|
||||||
self.logo_surface = cairo.ImageSurface.create_from_png(logo_path)
|
self.logo_surface = cairo.ImageSurface.create_from_png(logo_path)
|
||||||
|
|
|
@ -3,6 +3,7 @@ import os
|
||||||
import re
|
import re
|
||||||
import time
|
import time
|
||||||
import subprocess
|
import subprocess
|
||||||
|
import paths_factory
|
||||||
|
|
||||||
from i18n import _
|
from i18n import _
|
||||||
|
|
||||||
|
@ -22,7 +23,7 @@ class OnboardingWindow(gtk.Window):
|
||||||
self.connect("delete_event", self.exit)
|
self.connect("delete_event", self.exit)
|
||||||
|
|
||||||
self.builder = gtk.Builder()
|
self.builder = gtk.Builder()
|
||||||
self.builder.add_from_file("./onboarding.glade")
|
self.builder.add_from_file(paths_factory.onboarding_wireframe_path())
|
||||||
self.builder.connect_signals(self)
|
self.builder.connect_signals(self)
|
||||||
|
|
||||||
self.window = self.builder.get_object("onboardingwindow")
|
self.window = self.builder.get_object("onboardingwindow")
|
||||||
|
@ -67,29 +68,17 @@ class OnboardingWindow(gtk.Window):
|
||||||
self.execute_slide6()
|
self.execute_slide6()
|
||||||
|
|
||||||
def execute_slide1(self):
|
def execute_slide1(self):
|
||||||
conf_path = "/etc/howdy"
|
|
||||||
|
|
||||||
self.downloadoutputlabel = self.builder.get_object("downloadoutputlabel")
|
self.downloadoutputlabel = self.builder.get_object("downloadoutputlabel")
|
||||||
eventbox = self.builder.get_object("downloadeventbox")
|
eventbox = self.builder.get_object("downloadeventbox")
|
||||||
eventbox.modify_bg(gtk.StateType.NORMAL, gdk.Color(red=0, green=0, blue=0))
|
eventbox.modify_bg(gtk.StateType.NORMAL, gdk.Color(red=0, green=0, blue=0))
|
||||||
|
|
||||||
for lib_site in ("/lib", "/usr/lib", "/lib64", "/usr/lib64"):
|
# TODO: Better way to do this?
|
||||||
if os.path.exists(lib_site + "/security/howdy/"):
|
if os.path.exists(paths_factory.dlib_data_dir_path() / "shape_predictor_5_face_landmarks.dat"):
|
||||||
break
|
|
||||||
else:
|
|
||||||
lib_site = None
|
|
||||||
|
|
||||||
if lib_site is None:
|
|
||||||
self.downloadoutputlabel.set_text(_("Unable to find Howdy's installation location"))
|
|
||||||
return
|
|
||||||
|
|
||||||
|
|
||||||
if os.path.exists(conf_path + "/dlib-data/shape_predictor_5_face_landmarks.dat"):
|
|
||||||
self.downloadoutputlabel.set_text(_("Datafiles have already been downloaded!\nClick Next to continue"))
|
self.downloadoutputlabel.set_text(_("Datafiles have already been downloaded!\nClick Next to continue"))
|
||||||
self.enable_next()
|
self.enable_next()
|
||||||
return
|
return
|
||||||
|
|
||||||
self.proc = subprocess.Popen("./install.sh", stdout=subprocess.PIPE, stderr=subprocess.STDOUT, shell=True, cwd=conf_path + "/howdy/dlib-data")
|
self.proc = subprocess.Popen("./install.sh", stdout=subprocess.PIPE, stderr=subprocess.STDOUT, shell=True, cwd=paths_factory.dlib_data_dir_path())
|
||||||
|
|
||||||
self.download_lines = []
|
self.download_lines = []
|
||||||
self.read_download_line()
|
self.read_download_line()
|
||||||
|
|
13
howdy-gtk/src/paths.py.in
Normal file
13
howdy-gtk/src/paths.py.in
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
from pathlib import PurePath
|
||||||
|
|
||||||
|
# Define the absolute path to the config directory
|
||||||
|
config_dir = PurePath("@config_dir@")
|
||||||
|
|
||||||
|
# Define the absolute path to the DLib models data directory
|
||||||
|
dlib_data_dir = PurePath("@dlib_data_dir@")
|
||||||
|
|
||||||
|
# Define the absolute path to the Howdy user models directory
|
||||||
|
user_models_dir = PurePath("@user_models_dir@")
|
||||||
|
|
||||||
|
# Define the absolute path to the Howdy data directory
|
||||||
|
data_dir = PurePath("@data_dir@")
|
32
howdy-gtk/src/paths_factory.py
Normal file
32
howdy-gtk/src/paths_factory.py
Normal file
|
@ -0,0 +1,32 @@
|
||||||
|
from pathlib import PurePath
|
||||||
|
import paths
|
||||||
|
|
||||||
|
|
||||||
|
def config_file_path() -> str:
|
||||||
|
"""Return the path to the config file"""
|
||||||
|
return str(paths.config_dir / "config.ini")
|
||||||
|
|
||||||
|
|
||||||
|
def user_models_dir_path() -> PurePath:
|
||||||
|
"""Return the path to the user models directory"""
|
||||||
|
return paths.user_models_dir
|
||||||
|
|
||||||
|
|
||||||
|
def logo_path() -> str:
|
||||||
|
"""Return the path to the logo file"""
|
||||||
|
return str(paths.data_dir / "logo.png")
|
||||||
|
|
||||||
|
|
||||||
|
def onboarding_wireframe_path() -> str:
|
||||||
|
"""Return the path to the onboarding wireframe file"""
|
||||||
|
return str(paths.data_dir / "onboarding.glade")
|
||||||
|
|
||||||
|
|
||||||
|
def main_window_wireframe_path() -> str:
|
||||||
|
"""Return the path to the main window wireframe file"""
|
||||||
|
return str(paths.data_dir / "main.glade")
|
||||||
|
|
||||||
|
|
||||||
|
def dlib_data_dir_path() -> PurePath:
|
||||||
|
"""Return the path to the dlib data directory"""
|
||||||
|
return paths.dlib_data_dir
|
|
@ -1,6 +1,7 @@
|
||||||
import configparser
|
import configparser
|
||||||
|
|
||||||
from i18n import _
|
from i18n import _
|
||||||
|
import paths_factory
|
||||||
|
|
||||||
from gi.repository import Gtk as gtk
|
from gi.repository import Gtk as gtk
|
||||||
from gi.repository import Gdk as gdk
|
from gi.repository import Gdk as gdk
|
||||||
|
@ -17,7 +18,7 @@ def on_page_switch(self, notebook, page, page_num):
|
||||||
|
|
||||||
try:
|
try:
|
||||||
self.config = configparser.ConfigParser()
|
self.config = configparser.ConfigParser()
|
||||||
self.config.read("/etc/howdy/config.ini")
|
self.config.read(paths_factory.config_file_path())
|
||||||
except Exception:
|
except Exception:
|
||||||
print(_("Can't open camera"))
|
print(_("Can't open camera"))
|
||||||
|
|
||||||
|
|
|
@ -7,6 +7,7 @@ import elevate
|
||||||
import subprocess
|
import subprocess
|
||||||
|
|
||||||
from i18n import _
|
from i18n import _
|
||||||
|
import paths_factory
|
||||||
|
|
||||||
# Make sure we have the libs we need
|
# Make sure we have the libs we need
|
||||||
gi.require_version("Gtk", "3.0")
|
gi.require_version("Gtk", "3.0")
|
||||||
|
@ -26,7 +27,7 @@ class MainWindow(gtk.Window):
|
||||||
self.connect("delete_event", self.exit)
|
self.connect("delete_event", self.exit)
|
||||||
|
|
||||||
self.builder = gtk.Builder()
|
self.builder = gtk.Builder()
|
||||||
self.builder.add_from_file("./main.glade")
|
self.builder.add_from_file(paths_factory.main_window_wireframe_path())
|
||||||
self.builder.connect_signals(self)
|
self.builder.connect_signals(self)
|
||||||
|
|
||||||
self.window = self.builder.get_object("mainwindow")
|
self.window = self.builder.get_object("mainwindow")
|
||||||
|
@ -49,7 +50,7 @@ class MainWindow(gtk.Window):
|
||||||
# Add the treeview
|
# Add the treeview
|
||||||
self.modellistbox.add(self.treeview)
|
self.modellistbox.add(self.treeview)
|
||||||
|
|
||||||
filelist = os.listdir("/etc/howdy/models")
|
filelist = os.listdir(paths_factory.user_models_dir_path())
|
||||||
self.active_user = ""
|
self.active_user = ""
|
||||||
|
|
||||||
self.userlist.items = 0
|
self.userlist.items = 0
|
||||||
|
@ -120,7 +121,7 @@ signal.signal(signal.SIGINT, signal.SIG_DFL)
|
||||||
elevate.elevate()
|
elevate.elevate()
|
||||||
|
|
||||||
# If no models have been created yet or when it is forced, start the onboarding
|
# 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("/etc/howdy/models"):
|
if "--force-onboarding" in sys.argv or not os.path.exists(paths_factory.user_models_dir_path()):
|
||||||
import onboarding
|
import onboarding
|
||||||
onboarding.OnboardingWindow()
|
onboarding.OnboardingWindow()
|
||||||
|
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
debian/howdy.1
|
howdy.1
|
||||||
|
|
1
howdy/meson.build
Normal file
1
howdy/meson.build
Normal file
|
@ -0,0 +1 @@
|
||||||
|
subdir('src')
|
|
@ -4,6 +4,8 @@
|
||||||
|
|
||||||
_howdy() {
|
_howdy() {
|
||||||
local cur prev opts
|
local cur prev opts
|
||||||
|
local config_path="@config_path@"
|
||||||
|
source _variables
|
||||||
COMPREPLY=()
|
COMPREPLY=()
|
||||||
# The argument typed so far
|
# The argument typed so far
|
||||||
cur="${COMP_WORDS[COMP_CWORD]}"
|
cur="${COMP_WORDS[COMP_CWORD]}"
|
||||||
|
@ -20,7 +22,7 @@ _howdy() {
|
||||||
;;
|
;;
|
||||||
# For disable, grab the current "disabled" config option and give the reverse
|
# For disable, grab the current "disabled" config option and give the reverse
|
||||||
"disable")
|
"disable")
|
||||||
local status=$(cut -d'=' -f2 <<< $(cat /etc/howdy/config.ini | grep 'disabled =') | xargs echo -n)
|
local status=$(cut -d'=' -f2 <<< $(cat $config_path | grep 'disabled =') | xargs echo -n)
|
||||||
|
|
||||||
[ "$status" == "false" ] && COMPREPLY="true" || COMPREPLY="false"
|
[ "$status" == "false" ] && COMPREPLY="true" || COMPREPLY="false"
|
||||||
return 0
|
return 0
|
3
howdy/src/bin/howdy.in
Normal file
3
howdy/src/bin/howdy.in
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
env python3 "@script_path@" "$@"
|
|
@ -8,6 +8,7 @@ import json
|
||||||
import configparser
|
import configparser
|
||||||
import builtins
|
import builtins
|
||||||
import numpy as np
|
import numpy as np
|
||||||
|
import paths_factory
|
||||||
|
|
||||||
from recorders.video_capture import VideoCapture
|
from recorders.video_capture import VideoCapture
|
||||||
from i18n import _
|
from i18n import _
|
||||||
|
@ -26,39 +27,36 @@ except ImportError as err:
|
||||||
# OpenCV needs to be imported after dlib
|
# OpenCV needs to be imported after dlib
|
||||||
import cv2
|
import cv2
|
||||||
|
|
||||||
# Define the absolute path to the config directory
|
|
||||||
config_path = "/etc/howdy"
|
|
||||||
|
|
||||||
# Test if at lest 1 of the data files is there and abort if it's not
|
# Test if at lest 1 of the data files is there and abort if it's not
|
||||||
if not os.path.isfile(config_path + "/dlib-data/shape_predictor_5_face_landmarks.dat"):
|
if not os.path.isfile(paths_factory.shape_predictor_5_face_landmarks_path()):
|
||||||
print(_("Data files have not been downloaded, please run the following commands:"))
|
print(_("Data files have not been downloaded, please run the following commands:"))
|
||||||
print("\n\tcd " + config_path + "/dlib-data")
|
print("\n\tcd " + paths_factory.dlib_data_dir_path())
|
||||||
print("\tsudo ./install.sh\n")
|
print("\tsudo ./install.sh\n")
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
# Read config from disk
|
# Read config from disk
|
||||||
config = configparser.ConfigParser()
|
config = configparser.ConfigParser()
|
||||||
config.read(config_path + "/config.ini")
|
config.read(paths_factory.config_file_path())
|
||||||
|
|
||||||
use_cnn = config.getboolean("core", "use_cnn", fallback=False)
|
use_cnn = config.getboolean("core", "use_cnn", fallback=False)
|
||||||
if use_cnn:
|
if use_cnn:
|
||||||
face_detector = dlib.cnn_face_detection_model_v1(config_path + "/dlib-data/mmod_human_face_detector.dat")
|
face_detector = dlib.cnn_face_detection_model_v1(paths_factory.mmod_human_face_detector_path())
|
||||||
else:
|
else:
|
||||||
face_detector = dlib.get_frontal_face_detector()
|
face_detector = dlib.get_frontal_face_detector()
|
||||||
|
|
||||||
pose_predictor = dlib.shape_predictor(config_path + "/dlib-data/shape_predictor_5_face_landmarks.dat")
|
pose_predictor = dlib.shape_predictor(paths_factory.shape_predictor_5_face_landmarks_path())
|
||||||
face_encoder = dlib.face_recognition_model_v1(config_path + "/dlib-data/dlib_face_recognition_resnet_model_v1.dat")
|
face_encoder = dlib.face_recognition_model_v1(paths_factory.dlib_face_recognition_resnet_model_v1_path())
|
||||||
|
|
||||||
user = builtins.howdy_user
|
user = builtins.howdy_user
|
||||||
# The permanent file to store the encoded model in
|
# The permanent file to store the encoded model in
|
||||||
enc_file = config_path + "/models/" + user + ".dat"
|
enc_file = paths_factory.user_model_path(user)
|
||||||
# Known encodings
|
# Known encodings
|
||||||
encodings = []
|
encodings = []
|
||||||
|
|
||||||
# Make the ./models folder if it doesn't already exist
|
# Make the ./models folder if it doesn't already exist
|
||||||
if not os.path.exists(config_path + "/models"):
|
if not os.path.exists(paths_factory.user_models_dir_path()):
|
||||||
print(_("No face model folder found, creating one"))
|
print(_("No face model folder found, creating one"))
|
||||||
os.makedirs(config_path + "/models")
|
os.makedirs(paths_factory.user_models_dir_path())
|
||||||
|
|
||||||
# To try read a premade encodings file if it exists
|
# To try read a premade encodings file if it exists
|
||||||
try:
|
try:
|
||||||
|
|
|
@ -4,21 +4,20 @@
|
||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
import builtins
|
import builtins
|
||||||
|
import paths_factory
|
||||||
|
|
||||||
from i18n import _
|
from i18n import _
|
||||||
|
|
||||||
# Get the full path to this file
|
|
||||||
path = "/etc/howdy/models"
|
|
||||||
# Get the passed user
|
# Get the passed user
|
||||||
user = builtins.howdy_user
|
user = builtins.howdy_user
|
||||||
|
|
||||||
# Check if the models folder is there
|
# Check if the models folder is there
|
||||||
if not os.path.exists(path):
|
if not os.path.exists(paths_factory.user_models_dir_path()):
|
||||||
print(_("No models created yet, can't clear them if they don't exist"))
|
print(_("No models created yet, can't clear them if they don't exist"))
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
# Check if the user has a models file to delete
|
# Check if the user has a models file to delete
|
||||||
if not os.path.isfile(path + "/" + user + ".dat"):
|
if not os.path.isfile(paths_factory.user_model_path(user)):
|
||||||
print(_("{} has no models or they have been cleared already").format(user))
|
print(_("{} has no models or they have been cleared already").format(user))
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
|
@ -34,5 +33,5 @@ if not builtins.howdy_args.y:
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
# Delete otherwise
|
# Delete otherwise
|
||||||
os.remove(path + "/" + user + ".dat")
|
os.remove(paths_factory.user_model_path(user))
|
||||||
print(_("\nModels cleared"))
|
print(_("\nModels cleared"))
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
# Import required modules
|
# Import required modules
|
||||||
import os
|
import os
|
||||||
import subprocess
|
import subprocess
|
||||||
|
import paths_factory
|
||||||
|
|
||||||
from i18n import _
|
from i18n import _
|
||||||
|
|
||||||
|
@ -19,4 +20,4 @@ elif os.path.isfile("/etc/alternatives/editor"):
|
||||||
editor = "/etc/alternatives/editor"
|
editor = "/etc/alternatives/editor"
|
||||||
|
|
||||||
# Open the editor as a subprocess and fork it
|
# Open the editor as a subprocess and fork it
|
||||||
subprocess.call([editor, "/etc/howdy/config.ini"])
|
subprocess.call([editor, paths_factory.config_file_path()])
|
||||||
|
|
|
@ -6,11 +6,12 @@ import os
|
||||||
import builtins
|
import builtins
|
||||||
import fileinput
|
import fileinput
|
||||||
import configparser
|
import configparser
|
||||||
|
import paths_factory
|
||||||
|
|
||||||
from i18n import _
|
from i18n import _
|
||||||
|
|
||||||
# Get the absolute filepath
|
# Get the absolute filepath
|
||||||
config_path = os.path.dirname("/etc/howdy/") + "/config.ini"
|
config_path = paths_factory.config_file_path()
|
||||||
|
|
||||||
# Read config from disk
|
# Read config from disk
|
||||||
config = configparser.ConfigParser()
|
config = configparser.ConfigParser()
|
||||||
|
|
|
@ -6,21 +6,20 @@ import os
|
||||||
import json
|
import json
|
||||||
import time
|
import time
|
||||||
import builtins
|
import builtins
|
||||||
|
import paths_factory
|
||||||
|
|
||||||
from i18n import _
|
from i18n import _
|
||||||
|
|
||||||
# Get the absolute path and the username
|
|
||||||
path = "/etc/howdy"
|
|
||||||
user = builtins.howdy_user
|
user = builtins.howdy_user
|
||||||
|
|
||||||
# Check if the models file has been created yet
|
# Check if the models file has been created yet
|
||||||
if not os.path.exists(path + "/models"):
|
if not os.path.exists(paths_factory.user_models_dir_path()):
|
||||||
print(_("Face models have not been initialized yet, please run:"))
|
print(_("Face models have not been initialized yet, please run:"))
|
||||||
print("\n\tsudo howdy -U " + user + " add\n")
|
print("\n\tsudo howdy -U " + user + " add\n")
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
# Path to the models file
|
# Path to the models file
|
||||||
enc_file = path + "/models/" + user + ".dat"
|
enc_file = paths_factory.user_model_path(user)
|
||||||
|
|
||||||
# Try to load the models file and abort if the user does not have it yet
|
# Try to load the models file and abort if the user does not have it yet
|
||||||
try:
|
try:
|
||||||
|
|
|
@ -5,11 +5,10 @@ import sys
|
||||||
import os
|
import os
|
||||||
import json
|
import json
|
||||||
import builtins
|
import builtins
|
||||||
|
import paths_factory
|
||||||
|
|
||||||
from i18n import _
|
from i18n import _
|
||||||
|
|
||||||
# Get the absolute path and the username
|
|
||||||
path = "/etc/howdy"
|
|
||||||
user = builtins.howdy_user
|
user = builtins.howdy_user
|
||||||
|
|
||||||
# Check if enough arguments have been passed
|
# Check if enough arguments have been passed
|
||||||
|
@ -22,13 +21,13 @@ if not builtins.howdy_args.arguments:
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
# Check if the models file has been created yet
|
# Check if the models file has been created yet
|
||||||
if not os.path.exists(path + "/models"):
|
if not os.path.exists(paths_factory.user_models_dir_path()):
|
||||||
print(_("Face models have not been initialized yet, please run:"))
|
print(_("Face models have not been initialized yet, please run:"))
|
||||||
print("\n\thowdy add\n")
|
print("\n\thowdy add\n")
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
# Path to the models file
|
# Path to the models file
|
||||||
enc_file = path + "/models/" + user + ".dat"
|
enc_file = paths_factory.user_model_path(user)
|
||||||
|
|
||||||
# Try to load the models file and abort if the user does not have it yet
|
# Try to load the models file and abort if the user does not have it yet
|
||||||
try:
|
try:
|
||||||
|
@ -72,7 +71,7 @@ if not found:
|
||||||
|
|
||||||
# Remove the entire file if this encoding is the only one
|
# Remove the entire file if this encoding is the only one
|
||||||
if len(encodings) == 1:
|
if len(encodings) == 1:
|
||||||
os.remove(path + "/models/" + user + ".dat")
|
os.remove(paths_factory.user_model_path(user))
|
||||||
print(_("Removed last model, howdy disabled for user"))
|
print(_("Removed last model, howdy disabled for user"))
|
||||||
else:
|
else:
|
||||||
# A place holder to contain the encodings that will remain
|
# A place holder to contain the encodings that will remain
|
||||||
|
|
|
@ -5,11 +5,12 @@ import sys
|
||||||
import os
|
import os
|
||||||
import builtins
|
import builtins
|
||||||
import fileinput
|
import fileinput
|
||||||
|
import paths_factory
|
||||||
|
|
||||||
from i18n import _
|
from i18n import _
|
||||||
|
|
||||||
# Get the absolute filepath
|
# Get the absolute filepath
|
||||||
config_path = os.path.dirname("/etc/howdy/") + "/config.ini"
|
config_path = paths_factory.config_file_path()
|
||||||
|
|
||||||
# Check if enough arguments have been passed
|
# Check if enough arguments have been passed
|
||||||
if len(builtins.howdy_args.arguments) < 2:
|
if len(builtins.howdy_args.arguments) < 2:
|
||||||
|
|
|
@ -5,15 +5,14 @@ import os
|
||||||
import configparser
|
import configparser
|
||||||
import datetime
|
import datetime
|
||||||
import snapshot
|
import snapshot
|
||||||
|
import paths_factory
|
||||||
from recorders.video_capture import VideoCapture
|
from recorders.video_capture import VideoCapture
|
||||||
|
|
||||||
from i18n import _
|
from i18n import _
|
||||||
|
|
||||||
path = "/etc/howdy"
|
|
||||||
|
|
||||||
# Read the config
|
# Read the config
|
||||||
config = configparser.ConfigParser()
|
config = configparser.ConfigParser()
|
||||||
config.read(path + "/config.ini")
|
config.read(paths_factory.config_file_path())
|
||||||
|
|
||||||
# Start video capture
|
# Start video capture
|
||||||
video_capture = VideoCapture(config)
|
video_capture = VideoCapture(config)
|
||||||
|
|
|
@ -10,16 +10,14 @@ import time
|
||||||
import dlib
|
import dlib
|
||||||
import cv2
|
import cv2
|
||||||
import numpy as np
|
import numpy as np
|
||||||
|
import paths_factory
|
||||||
|
|
||||||
from i18n import _
|
from i18n import _
|
||||||
from recorders.video_capture import VideoCapture
|
from recorders.video_capture import VideoCapture
|
||||||
|
|
||||||
# The absolute path to the config directory
|
|
||||||
path = "/etc/howdy"
|
|
||||||
|
|
||||||
# Read config from disk
|
# Read config from disk
|
||||||
config = configparser.ConfigParser()
|
config = configparser.ConfigParser()
|
||||||
config.read(path + "/config.ini")
|
config.read(paths_factory.config_file_path())
|
||||||
|
|
||||||
if config.get("video", "recording_plugin", fallback="opencv") != "opencv":
|
if config.get("video", "recording_plugin", fallback="opencv") != "opencv":
|
||||||
print(_("Howdy has been configured to use a recorder which doesn't support the test command yet, aborting"))
|
print(_("Howdy has been configured to use a recorder which doesn't support the test command yet, aborting"))
|
||||||
|
@ -59,20 +57,20 @@ use_cnn = config.getboolean('core', 'use_cnn', fallback=False)
|
||||||
|
|
||||||
if use_cnn:
|
if use_cnn:
|
||||||
face_detector = dlib.cnn_face_detection_model_v1(
|
face_detector = dlib.cnn_face_detection_model_v1(
|
||||||
path + "/dlib-data/mmod_human_face_detector.dat"
|
paths_factory.mmod_human_face_detector_path()
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
face_detector = dlib.get_frontal_face_detector()
|
face_detector = dlib.get_frontal_face_detector()
|
||||||
|
|
||||||
pose_predictor = dlib.shape_predictor(path + "/dlib-data/shape_predictor_5_face_landmarks.dat")
|
pose_predictor = dlib.shape_predictor(paths_factory.shape_predictor_5_face_landmarks_path())
|
||||||
face_encoder = dlib.face_recognition_model_v1(path + "/dlib-data/dlib_face_recognition_resnet_model_v1.dat")
|
face_encoder = dlib.face_recognition_model_v1(paths_factory.dlib_face_recognition_resnet_model_v1_path())
|
||||||
|
|
||||||
encodings = []
|
encodings = []
|
||||||
models = None
|
models = None
|
||||||
|
|
||||||
try:
|
try:
|
||||||
user = builtins.howdy_user
|
user = builtins.howdy_user
|
||||||
models = json.load(open(path + "/models/" + user + ".dat"))
|
models = json.load(open(paths_factory.user_model_path(user)))
|
||||||
|
|
||||||
for model in models:
|
for model in models:
|
||||||
encodings += model["data"]
|
encodings += model["data"]
|
||||||
|
|
|
@ -23,10 +23,7 @@ import subprocess
|
||||||
import snapshot
|
import snapshot
|
||||||
import numpy as np
|
import numpy as np
|
||||||
import _thread as thread
|
import _thread as thread
|
||||||
|
import paths_factory
|
||||||
# Allow imports from the local howdy folder
|
|
||||||
sys.path.append('/lib/security/howdy')
|
|
||||||
|
|
||||||
from recorders.video_capture import VideoCapture
|
from recorders.video_capture import VideoCapture
|
||||||
from i18n import _
|
from i18n import _
|
||||||
|
|
||||||
|
@ -48,22 +45,22 @@ def init_detector(lock):
|
||||||
global face_detector, pose_predictor, face_encoder
|
global face_detector, pose_predictor, face_encoder
|
||||||
|
|
||||||
# Test if at lest 1 of the data files is there and abort if it's not
|
# Test if at lest 1 of the data files is there and abort if it's not
|
||||||
if not os.path.isfile(PATH + "/dlib-data/shape_predictor_5_face_landmarks.dat"):
|
if not os.path.isfile(paths_factory.shape_predictor_5_face_landmarks_path()):
|
||||||
print(_("Data files have not been downloaded, please run the following commands:"))
|
print(_("Data files have not been downloaded, please run the following commands:"))
|
||||||
print("\n\tcd " + PATH + "/dlib-data")
|
print("\n\tcd " + paths_factory.dlib_data_dir_path())
|
||||||
print("\tsudo ./install.sh\n")
|
print("\tsudo ./install.sh\n")
|
||||||
lock.release()
|
lock.release()
|
||||||
exit(1)
|
exit(1)
|
||||||
|
|
||||||
# Use the CNN detector if enabled
|
# Use the CNN detector if enabled
|
||||||
if use_cnn:
|
if use_cnn:
|
||||||
face_detector = dlib.cnn_face_detection_model_v1(PATH + "/dlib-data/mmod_human_face_detector.dat")
|
face_detector = dlib.cnn_face_detection_model_v1(paths_factory.mmod_human_face_detector_path())
|
||||||
else:
|
else:
|
||||||
face_detector = dlib.get_frontal_face_detector()
|
face_detector = dlib.get_frontal_face_detector()
|
||||||
|
|
||||||
# Start the others regardless
|
# Start the others regardless
|
||||||
pose_predictor = dlib.shape_predictor(PATH + "/dlib-data/shape_predictor_5_face_landmarks.dat")
|
pose_predictor = dlib.shape_predictor(paths_factory.shape_predictor_5_face_landmarks_path())
|
||||||
face_encoder = dlib.face_recognition_model_v1(PATH + "/dlib-data/dlib_face_recognition_resnet_model_v1.dat")
|
face_encoder = dlib.face_recognition_model_v1(paths_factory.dlib_face_recognition_resnet_model_v1_path())
|
||||||
|
|
||||||
# Note the time it took to initialize detectors
|
# Note the time it took to initialize detectors
|
||||||
timings["ll"] = time.time() - timings["ll"]
|
timings["ll"] = time.time() - timings["ll"]
|
||||||
|
@ -103,9 +100,6 @@ def send_to_ui(type, message):
|
||||||
if len(sys.argv) < 2:
|
if len(sys.argv) < 2:
|
||||||
exit(12)
|
exit(12)
|
||||||
|
|
||||||
# Get the absolute path to the config directory
|
|
||||||
PATH = "/etc/howdy"
|
|
||||||
|
|
||||||
# The username of the user being authenticated
|
# The username of the user being authenticated
|
||||||
user = sys.argv[1]
|
user = sys.argv[1]
|
||||||
# The model file contents
|
# The model file contents
|
||||||
|
@ -129,7 +123,7 @@ face_encoder = None
|
||||||
|
|
||||||
# Try to load the face model from the models folder
|
# Try to load the face model from the models folder
|
||||||
try:
|
try:
|
||||||
models = json.load(open(PATH + "/models/" + user + ".dat"))
|
models = json.load(open(paths_factory.user_model_path(user)))
|
||||||
|
|
||||||
for model in models:
|
for model in models:
|
||||||
encodings += model["data"]
|
encodings += model["data"]
|
||||||
|
@ -142,7 +136,7 @@ if len(models) < 1:
|
||||||
|
|
||||||
# Read config from disk
|
# Read config from disk
|
||||||
config = configparser.ConfigParser()
|
config = configparser.ConfigParser()
|
||||||
config.read(PATH + "/config.ini")
|
config.read(paths_factory.config_file_path())
|
||||||
|
|
||||||
# Get all config values needed
|
# Get all config values needed
|
||||||
use_cnn = config.getboolean("core", "use_cnn", fallback=False)
|
use_cnn = config.getboolean("core", "use_cnn", fallback=False)
|
||||||
|
@ -160,7 +154,7 @@ gtk_pipe = sys.stdout if gtk_stdout else subprocess.DEVNULL
|
||||||
|
|
||||||
# Start the auth ui, register it to be always be closed on exit
|
# Start the auth ui, register it to be always be closed on exit
|
||||||
try:
|
try:
|
||||||
gtk_proc = subprocess.Popen(["../howdy-gtk/src/init.py", "--start-auth-ui"], stdin=subprocess.PIPE, stdout=gtk_pipe, stderr=gtk_pipe)
|
gtk_proc = subprocess.Popen(["howdy-gtk", "--start-auth-ui"], stdin=subprocess.PIPE, stdout=gtk_pipe, stderr=gtk_pipe)
|
||||||
atexit.register(exit)
|
atexit.register(exit)
|
||||||
except FileNotFoundError:
|
except FileNotFoundError:
|
||||||
pass
|
pass
|
||||||
|
|
170
howdy/src/meson.build
Normal file
170
howdy/src/meson.build
Normal file
|
@ -0,0 +1,170 @@
|
||||||
|
if meson.is_subproject()
|
||||||
|
project('howdy', 'cpp', license: 'MIT', version: 'beta', meson_version: '>= 0.64.0')
|
||||||
|
endif
|
||||||
|
|
||||||
|
py = import('python').find_installation()
|
||||||
|
py.dependency()
|
||||||
|
|
||||||
|
datadir = get_option('prefix') / get_option('datadir') / 'howdy'
|
||||||
|
py_conf = configuration_data(paths_dict)
|
||||||
|
py_conf.set('data_dir', datadir)
|
||||||
|
|
||||||
|
py_paths = configure_file(
|
||||||
|
input: 'paths.py.in',
|
||||||
|
output: 'paths.py',
|
||||||
|
configuration: py_conf,
|
||||||
|
)
|
||||||
|
|
||||||
|
py_sources = [
|
||||||
|
'cli/__init__.py',
|
||||||
|
'cli/add.py',
|
||||||
|
'cli/clear.py',
|
||||||
|
'cli/config.py',
|
||||||
|
'cli/disable.py',
|
||||||
|
'cli/list.py',
|
||||||
|
'cli/remove.py',
|
||||||
|
'cli/set.py',
|
||||||
|
'cli/snap.py',
|
||||||
|
'cli/test.py',
|
||||||
|
'cli.py',
|
||||||
|
'compare.py',
|
||||||
|
'i18n.py',
|
||||||
|
'paths_factory.py',
|
||||||
|
'recorders/__init__.py',
|
||||||
|
'recorders/ffmpeg_reader.py',
|
||||||
|
'recorders/pyv4l2_reader.py',
|
||||||
|
'recorders/v4l2.py',
|
||||||
|
'recorders/video_capture.py',
|
||||||
|
'rubberstamps/__init__.py',
|
||||||
|
'rubberstamps/hotkey.py',
|
||||||
|
'rubberstamps/nod.py',
|
||||||
|
'snapshot.py',
|
||||||
|
py_paths,
|
||||||
|
]
|
||||||
|
|
||||||
|
# Include PAM module
|
||||||
|
if get_option('install_in_site_packages')
|
||||||
|
pysourcesinstalldir = join_paths(py.get_install_dir(), 'howdy')
|
||||||
|
else
|
||||||
|
pysourcesinstalldir = get_option('py_sources_dir') != '' ? get_option('py_sources_dir') : join_paths(get_option('prefix'), get_option('libdir'), 'howdy')
|
||||||
|
endif
|
||||||
|
|
||||||
|
pam_module_conf_data = configuration_data(paths_dict)
|
||||||
|
pam_module_conf_data.set('compare_script_path', join_paths(pysourcesinstalldir, 'compare.py'))
|
||||||
|
pam_module_conf_data.set('config_file_path', config_path)
|
||||||
|
subdir('pam')
|
||||||
|
if get_option('install_pam_config')
|
||||||
|
# pamdir is inherited from the pam subproject
|
||||||
|
pam_config = configure_file(
|
||||||
|
input: 'pam-config/howdy.in',
|
||||||
|
output: 'pam-config',
|
||||||
|
configuration: {'pamdir': pamdir}
|
||||||
|
)
|
||||||
|
install_data(
|
||||||
|
pam_config,
|
||||||
|
install_dir: get_option('prefix') / get_option('datadir') / 'pam-configs',
|
||||||
|
install_mode: 'rwxr-xr-x',
|
||||||
|
install_tag: 'pam',
|
||||||
|
rename: 'howdy',
|
||||||
|
)
|
||||||
|
endif
|
||||||
|
|
||||||
|
if get_option('install_in_site_packages')
|
||||||
|
py.install_sources(
|
||||||
|
py_sources,
|
||||||
|
subdir: 'howdy',
|
||||||
|
preserve_path: true,
|
||||||
|
install_mode: 'r--r--r--',
|
||||||
|
install_tag: 'py_sources',
|
||||||
|
)
|
||||||
|
else
|
||||||
|
install_data(
|
||||||
|
py_sources,
|
||||||
|
preserve_path: true,
|
||||||
|
install_dir: pysourcesinstalldir,
|
||||||
|
install_mode: 'r--r--r--',
|
||||||
|
install_tag: 'py_sources',
|
||||||
|
)
|
||||||
|
endif
|
||||||
|
|
||||||
|
install_data('logo.png', install_tag: 'meta')
|
||||||
|
autocomplete = configure_file(
|
||||||
|
input: 'autocomplete/howdy.in',
|
||||||
|
output: 'autocomplete',
|
||||||
|
configuration: configuration_data({ 'config_path': config_path })
|
||||||
|
)
|
||||||
|
install_data(
|
||||||
|
autocomplete,
|
||||||
|
install_dir: join_paths(get_option('prefix'), get_option('datadir'), 'bash-completion', 'completions'),
|
||||||
|
install_mode: 'rwxr--r--',
|
||||||
|
install_tag: 'bash_completion',
|
||||||
|
rename: 'howdy',
|
||||||
|
)
|
||||||
|
|
||||||
|
fs = import('fs')
|
||||||
|
if not fs.exists(config_path)
|
||||||
|
install_data('config.ini', install_dir: confdir, install_mode: 'rwxr--r--', install_tag: 'config')
|
||||||
|
endif
|
||||||
|
|
||||||
|
install_data('dlib-data/install.sh', 'dlib-data/Readme.md', install_dir: dlibdatadir, install_mode: 'rwxr--r--')
|
||||||
|
|
||||||
|
install_man('../howdy.1')
|
||||||
|
|
||||||
|
# if get_option('fetch_dlib_data')
|
||||||
|
# downloader = find_program('wget')
|
||||||
|
# bunzip2 = find_program('bunzip2')
|
||||||
|
|
||||||
|
# links = [
|
||||||
|
# 'https://github.com/davisking/dlib-models/raw/master/dlib_face_recognition_resnet_model_v1.dat.bz2',
|
||||||
|
# 'https://github.com/davisking/dlib-models/raw/master/mmod_human_face_detector.dat.bz2',
|
||||||
|
# 'https://github.com/davisking/dlib-models/raw/master/shape_predictor_5_face_landmarks.dat.bz2'
|
||||||
|
# ]
|
||||||
|
|
||||||
|
# archived_model_files = [
|
||||||
|
# 'dlib_face_recognition_resnet_model_v1.dat.bz2',
|
||||||
|
# 'shape_predictor_5_face_landmarks.dat.bz2',
|
||||||
|
# 'mmod_human_face_detector.dat.bz2'
|
||||||
|
# ]
|
||||||
|
|
||||||
|
# download = run_command(
|
||||||
|
# 'download',
|
||||||
|
# links,
|
||||||
|
# output: archived_model_files,
|
||||||
|
# command: [downloader, '-O', '@OUTPUT@', '@INPUT@']
|
||||||
|
# )
|
||||||
|
|
||||||
|
# model_files = [
|
||||||
|
# 'dlib_face_recognition_resnet_model_v1.dat',
|
||||||
|
# 'shape_predictor_5_face_landmarks.dat',
|
||||||
|
# 'mmod_human_face_detector.dat'
|
||||||
|
# ]
|
||||||
|
|
||||||
|
# models = custom_target(
|
||||||
|
# 'models',
|
||||||
|
# input: archived_model_files,
|
||||||
|
# output: model_files,
|
||||||
|
# command: [bunzip2, '-k', '@INPUT@'],
|
||||||
|
# )
|
||||||
|
|
||||||
|
# install_data(
|
||||||
|
# model_files,
|
||||||
|
# install_dir: join_paths(get_option('prefix'), get_option('libdir'), 'dlib_models'),
|
||||||
|
# )
|
||||||
|
|
||||||
|
# endif
|
||||||
|
|
||||||
|
cli_path = join_paths(pysourcesinstalldir, 'cli.py')
|
||||||
|
conf_data = configuration_data({ 'script_path': cli_path })
|
||||||
|
|
||||||
|
bin_name = 'howdy'
|
||||||
|
bin = configure_file(
|
||||||
|
input: 'bin/howdy.in',
|
||||||
|
output: bin_name,
|
||||||
|
configuration: conf_data
|
||||||
|
)
|
||||||
|
install_data(
|
||||||
|
bin,
|
||||||
|
install_mode: 'rwxr-xr-x',
|
||||||
|
install_dir: get_option('bindir'),
|
||||||
|
install_tag: 'bin',
|
||||||
|
)
|
|
@ -1,6 +0,0 @@
|
||||||
Name: Howdy
|
|
||||||
Default: yes
|
|
||||||
Priority: 512
|
|
||||||
Auth-Type: Primary
|
|
||||||
Auth:
|
|
||||||
[success=end default=ignore] /lib/security/howdy/pam_howdy.so
|
|
6
howdy/src/pam-config/howdy.in
Normal file
6
howdy/src/pam-config/howdy.in
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
Name: Howdy
|
||||||
|
Default: yes
|
||||||
|
Priority: 512
|
||||||
|
Auth-Type: Primary
|
||||||
|
Auth:
|
||||||
|
[success=end default=ignore] @pamdir@/pam_howdy.so
|
|
@ -1,7 +0,0 @@
|
||||||
Checks: 'clang-diagnostic-*,clang-analyser-*,-clang-diagnostic-unused-command-line-argument,google-*,bugprone-*,modernize-*,performance-*,portability-*,readability-*,-bugprone-easily-swappable-*,-readability-magic-numbers,-google-readability-todo'
|
|
||||||
WarningsAsErrors: 'clang-diagnostic-*,clang-analyser-*,-clang-diagnostic-unused-command-line-argument,google-*,bugprone-*,modernize-*,performance-*,portability-*,readability-*,-bugprone-easily-swappable-*,-readability-magic-numbers,-google-readability-todo'
|
|
||||||
CheckOptions:
|
|
||||||
- key: readability-function-cognitive-complexity.Threshold
|
|
||||||
value: '50'
|
|
||||||
|
|
||||||
# vim:syntax=yaml
|
|
1
howdy/src/pam/.clang-tidy
Symbolic link
1
howdy/src/pam/.clang-tidy
Symbolic link
|
@ -0,0 +1 @@
|
||||||
|
../.clang-tidy
|
|
@ -9,6 +9,7 @@
|
||||||
#include <spawn.h>
|
#include <spawn.h>
|
||||||
#include <stdexcept>
|
#include <stdexcept>
|
||||||
#include <sys/signalfd.h>
|
#include <sys/signalfd.h>
|
||||||
|
#include <sys/stat.h>
|
||||||
#include <sys/syslog.h>
|
#include <sys/syslog.h>
|
||||||
#include <sys/types.h>
|
#include <sys/types.h>
|
||||||
#include <sys/wait.h>
|
#include <sys/wait.h>
|
||||||
|
@ -41,12 +42,12 @@
|
||||||
#include "enter_device.hh"
|
#include "enter_device.hh"
|
||||||
#include "main.hh"
|
#include "main.hh"
|
||||||
#include "optional_task.hh"
|
#include "optional_task.hh"
|
||||||
|
#include "paths.hh"
|
||||||
|
|
||||||
const auto DEFAULT_TIMEOUT =
|
const auto DEFAULT_TIMEOUT =
|
||||||
std::chrono::duration<int, std::chrono::milliseconds::period>(100);
|
std::chrono::duration<int, std::chrono::milliseconds::period>(100);
|
||||||
const auto MAX_RETRIES = 5;
|
const auto MAX_RETRIES = 5;
|
||||||
const auto PYTHON_EXECUTABLE = "python3";
|
const auto PYTHON_EXECUTABLE = "python3";
|
||||||
const auto COMPARE_PROCESS_PATH = "/lib/security/howdy/compare.py";
|
|
||||||
|
|
||||||
#define S(msg) gettext(msg)
|
#define S(msg) gettext(msg)
|
||||||
|
|
||||||
|
@ -80,7 +81,8 @@ auto howdy_error(int status,
|
||||||
syslog(LOG_ERR, "Failure, image too dark");
|
syslog(LOG_ERR, "Failure, image too dark");
|
||||||
break;
|
break;
|
||||||
case CompareError::INVALID_DEVICE:
|
case CompareError::INVALID_DEVICE:
|
||||||
syslog(LOG_ERR, "Failure, not possible to open camera at configured path");
|
syslog(LOG_ERR,
|
||||||
|
"Failure, not possible to open camera at configured path");
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
conv_function(PAM_ERROR_MSG,
|
conv_function(PAM_ERROR_MSG,
|
||||||
|
@ -133,10 +135,11 @@ auto howdy_status(char *username, int status, const INIReader &config,
|
||||||
* Check if Howdy should be enabled according to the configuration and the
|
* Check if Howdy should be enabled according to the configuration and the
|
||||||
* environment.
|
* environment.
|
||||||
* @param config INI configuration
|
* @param config INI configuration
|
||||||
|
* @param username Username
|
||||||
* @return Returns PAM_AUTHINFO_UNAVAIL if it shouldn't be enabled,
|
* @return Returns PAM_AUTHINFO_UNAVAIL if it shouldn't be enabled,
|
||||||
* PAM_SUCCESS otherwise
|
* PAM_SUCCESS otherwise
|
||||||
*/
|
*/
|
||||||
auto check_enabled(const INIReader &config) -> int {
|
auto check_enabled(const INIReader &config, const char* username) -> int {
|
||||||
// Stop executing if Howdy has been disabled in the config
|
// Stop executing if Howdy has been disabled in the config
|
||||||
if (config.GetBoolean("core", "disabled", false)) {
|
if (config.GetBoolean("core", "disabled", false)) {
|
||||||
syslog(LOG_INFO, "Skipped authentication, Howdy is disabled");
|
syslog(LOG_INFO, "Skipped authentication, Howdy is disabled");
|
||||||
|
@ -182,6 +185,13 @@ auto check_enabled(const INIReader &config) -> int {
|
||||||
globfree(&glob_result);
|
globfree(&glob_result);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// pre-check if this user has face model file
|
||||||
|
auto model_path = std::string(USER_MODELS_DIR) + "/" + username + ".dat";
|
||||||
|
struct stat stat_;
|
||||||
|
if (stat(model_path.c_str(), &stat_) != 0) {
|
||||||
|
return PAM_AUTHINFO_UNAVAIL;
|
||||||
|
}
|
||||||
|
|
||||||
return PAM_SUCCESS;
|
return PAM_SUCCESS;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -191,12 +201,12 @@ auto check_enabled(const INIReader &config) -> int {
|
||||||
* @param flags Flags passed on to us by PAM, XORed
|
* @param flags Flags passed on to us by PAM, XORed
|
||||||
* @param argc Amount of rules in the PAM config (disregared)
|
* @param argc Amount of rules in the PAM config (disregared)
|
||||||
* @param argv Options defined in the PAM config
|
* @param argv Options defined in the PAM config
|
||||||
* @param auth_tok True if we should ask for a password too
|
* @param ask_auth_tok True if we should ask for a password too
|
||||||
* @return Returns a PAM return code
|
* @return Returns a PAM return code
|
||||||
*/
|
*/
|
||||||
auto identify(pam_handle_t *pamh, int flags, int argc, const char **argv,
|
auto identify(pam_handle_t *pamh, int flags, int argc, const char **argv,
|
||||||
bool auth_tok) -> int {
|
bool ask_auth_tok) -> int {
|
||||||
INIReader config("/etc/howdy/config.ini");
|
INIReader config(CONFIG_FILE_PATH);
|
||||||
openlog("pam_howdy", 0, LOG_AUTHPRIV);
|
openlog("pam_howdy", 0, LOG_AUTHPRIV);
|
||||||
|
|
||||||
// Error out if we could not read the config file
|
// Error out if we could not read the config file
|
||||||
|
@ -209,8 +219,16 @@ auto identify(pam_handle_t *pamh, int flags, int argc, const char **argv,
|
||||||
// Will contain the responses from PAM functions
|
// Will contain the responses from PAM functions
|
||||||
int pam_res = PAM_IGNORE;
|
int pam_res = PAM_IGNORE;
|
||||||
|
|
||||||
|
// Get the username from PAM, needed to match correct face model
|
||||||
|
char *username = nullptr;
|
||||||
|
if ((pam_res = pam_get_user(pamh, const_cast<const char **>(&username),
|
||||||
|
nullptr)) != PAM_SUCCESS) {
|
||||||
|
syslog(LOG_ERR, "Failed to get username");
|
||||||
|
return pam_res;
|
||||||
|
}
|
||||||
|
|
||||||
// Check if we should continue
|
// Check if we should continue
|
||||||
if ((pam_res = check_enabled(config)) != PAM_SUCCESS) {
|
if ((pam_res = check_enabled(config, username)) != PAM_SUCCESS) {
|
||||||
return pam_res;
|
return pam_res;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -243,21 +261,6 @@ auto identify(pam_handle_t *pamh, int flags, int argc, const char **argv,
|
||||||
bindtextdomain(GETTEXT_PACKAGE, LOCALEDIR);
|
bindtextdomain(GETTEXT_PACKAGE, LOCALEDIR);
|
||||||
textdomain(GETTEXT_PACKAGE);
|
textdomain(GETTEXT_PACKAGE);
|
||||||
|
|
||||||
// Get the username from PAM, needed to match correct face model
|
|
||||||
char *username = nullptr;
|
|
||||||
if ((pam_res = pam_get_user(pamh, const_cast<const char **>(&username),
|
|
||||||
nullptr)) != PAM_SUCCESS) {
|
|
||||||
syslog(LOG_ERR, "Failed to get username");
|
|
||||||
return pam_res;
|
|
||||||
}
|
|
||||||
|
|
||||||
// pre-check if this user has face model file
|
|
||||||
auto model_path = std::string("/etc/howdy/models/") + username + ".dat";
|
|
||||||
if (!std::ifstream(model_path)) {
|
|
||||||
return howdy_status(username, CompareError::NO_FACE_MODEL, config,
|
|
||||||
conv_function);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (config.GetBoolean("core", "detection_notice", true)) {
|
if (config.GetBoolean("core", "detection_notice", true)) {
|
||||||
if ((conv_function(PAM_TEXT_INFO, S("Attempting facial authentication"))) !=
|
if ((conv_function(PAM_TEXT_INFO, S("Attempting facial authentication"))) !=
|
||||||
PAM_SUCCESS) {
|
PAM_SUCCESS) {
|
||||||
|
@ -316,7 +319,7 @@ auto identify(pam_handle_t *pamh, int flags, int argc, const char **argv,
|
||||||
return std::tuple<int, char *>(pam_res, auth_tok_ptr);
|
return std::tuple<int, char *>(pam_res, auth_tok_ptr);
|
||||||
});
|
});
|
||||||
|
|
||||||
auto ask_pass = auth_tok && workaround != Workaround::Off;
|
auto ask_pass = ask_auth_tok && workaround != Workaround::Off;
|
||||||
|
|
||||||
// We ask for the password if the function requires it and if a workaround is
|
// We ask for the password if the function requires it and if a workaround is
|
||||||
// set
|
// set
|
||||||
|
@ -327,7 +330,8 @@ auto identify(pam_handle_t *pamh, int flags, int argc, const char **argv,
|
||||||
// Wait for the end either of the child or the password input
|
// Wait for the end either of the child or the password input
|
||||||
{
|
{
|
||||||
std::unique_lock<std::mutex> lock(mutx);
|
std::unique_lock<std::mutex> lock(mutx);
|
||||||
convar.wait(lock, [&] { return confirmation_type != ConfirmationType::Unset; });
|
convar.wait(lock,
|
||||||
|
[&] { return confirmation_type != ConfirmationType::Unset; });
|
||||||
}
|
}
|
||||||
|
|
||||||
// The password has been entered or an error has occurred
|
// The password has been entered or an error has occurred
|
||||||
|
|
|
@ -1,5 +1,3 @@
|
||||||
project('pam_howdy', 'cpp', version: '0.1.0', default_options: ['cpp_std=c++14'])
|
|
||||||
|
|
||||||
inih_cpp = dependency('INIReader', fallback: ['inih', 'INIReader_dep'])
|
inih_cpp = dependency('INIReader', fallback: ['inih', 'INIReader_dep'])
|
||||||
libevdev = dependency('libevdev')
|
libevdev = dependency('libevdev')
|
||||||
libpam = meson.get_compiler('cpp').find_library('pam')
|
libpam = meson.get_compiler('cpp').find_library('pam')
|
||||||
|
@ -8,6 +6,15 @@ threads = dependency('threads')
|
||||||
# Translations
|
# Translations
|
||||||
subdir('po')
|
subdir('po')
|
||||||
|
|
||||||
|
# Paths
|
||||||
|
paths_h = configure_file(
|
||||||
|
input: 'paths.hh.in',
|
||||||
|
output: 'paths.hh',
|
||||||
|
configuration: pam_module_conf_data
|
||||||
|
)
|
||||||
|
|
||||||
|
pamdir = get_option('pam_dir') != '' ? get_option('pam_dir') : join_paths(get_option('prefix'), get_option('libdir'), 'security')
|
||||||
|
|
||||||
shared_library(
|
shared_library(
|
||||||
'pam_howdy',
|
'pam_howdy',
|
||||||
'main.cc',
|
'main.cc',
|
||||||
|
@ -18,7 +25,11 @@ shared_library(
|
||||||
threads,
|
threads,
|
||||||
libevdev,
|
libevdev,
|
||||||
],
|
],
|
||||||
|
link_depends: [
|
||||||
|
paths_h,
|
||||||
|
],
|
||||||
install: true,
|
install: true,
|
||||||
install_dir: '/lib/security',
|
install_dir: pamdir,
|
||||||
|
install_tag: 'pam_module',
|
||||||
name_prefix: ''
|
name_prefix: ''
|
||||||
)
|
)
|
||||||
|
|
3
howdy/src/pam/paths.hh.in
Normal file
3
howdy/src/pam/paths.hh.in
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
const auto COMPARE_PROCESS_PATH = "@compare_script_path@";
|
||||||
|
const auto CONFIG_FILE_PATH = "@config_file_path@";
|
||||||
|
const auto USER_MODELS_DIR = "@user_models_dir@";
|
|
@ -6,5 +6,5 @@ localedir = '-DLOCALEDIR="@0@"'.format(get_option('prefix') / get_option('locale
|
||||||
add_project_arguments(gettext_package, localedir, language: 'cpp')
|
add_project_arguments(gettext_package, localedir, language: 'cpp')
|
||||||
|
|
||||||
i18n.gettext(meson.project_name(),
|
i18n.gettext(meson.project_name(),
|
||||||
args: [ '--directory=' + meson.source_root(), '--keyword=S:1' ]
|
args: [ '--directory=' + meson.current_source_dir(), '--keyword=S:1' ]
|
||||||
)
|
)
|
16
howdy/src/paths.py.in
Normal file
16
howdy/src/paths.py.in
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
from pathlib import PurePath
|
||||||
|
|
||||||
|
# Define the absolute path to the config directory
|
||||||
|
config_dir = PurePath("@config_dir@")
|
||||||
|
|
||||||
|
# Define the absolute path to the DLib models data directory
|
||||||
|
dlib_data_dir = PurePath("@dlib_data_dir@")
|
||||||
|
|
||||||
|
# Define the absolute path to the Howdy user models directory
|
||||||
|
user_models_dir = PurePath("@user_models_dir@")
|
||||||
|
|
||||||
|
# Define path to any howdy logs
|
||||||
|
log_path = PurePath("@log_path@")
|
||||||
|
|
||||||
|
# Define the absolute path to the Howdy data directory
|
||||||
|
data_dir = PurePath("@data_dir@")
|
48
howdy/src/paths_factory.py
Normal file
48
howdy/src/paths_factory.py
Normal file
|
@ -0,0 +1,48 @@
|
||||||
|
from pathlib import PurePath
|
||||||
|
import paths
|
||||||
|
|
||||||
|
models = [
|
||||||
|
"shape_predictor_5_face_landmarks.dat",
|
||||||
|
"mmod_human_face_detector.dat",
|
||||||
|
"dlib_face_recognition_resnet_model_v1.dat",
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
def dlib_data_dir_path() -> str:
|
||||||
|
return str(paths.dlib_data_dir)
|
||||||
|
|
||||||
|
|
||||||
|
def shape_predictor_5_face_landmarks_path() -> str:
|
||||||
|
return str(paths.dlib_data_dir / models[0])
|
||||||
|
|
||||||
|
|
||||||
|
def mmod_human_face_detector_path() -> str:
|
||||||
|
return str(paths.dlib_data_dir / models[1])
|
||||||
|
|
||||||
|
|
||||||
|
def dlib_face_recognition_resnet_model_v1_path() -> str:
|
||||||
|
return str(paths.dlib_data_dir / models[2])
|
||||||
|
|
||||||
|
|
||||||
|
def user_model_path(user: str) -> str:
|
||||||
|
return str(paths.user_models_dir / f"{user}.dat")
|
||||||
|
|
||||||
|
|
||||||
|
def config_file_path() -> str:
|
||||||
|
return str(paths.config_dir / "config.ini")
|
||||||
|
|
||||||
|
|
||||||
|
def snapshots_dir_path() -> PurePath:
|
||||||
|
return paths.log_path / "snapshots"
|
||||||
|
|
||||||
|
|
||||||
|
def snapshot_path(snapshot: str) -> str:
|
||||||
|
return str(snapshots_dir_path() / snapshot)
|
||||||
|
|
||||||
|
|
||||||
|
def user_models_dir_path() -> PurePath:
|
||||||
|
return paths.user_models_dir
|
||||||
|
|
||||||
|
|
||||||
|
def logo_path() -> str:
|
||||||
|
return str(paths.data_dir / "logo.png")
|
|
@ -5,6 +5,7 @@ import cv2
|
||||||
import os
|
import os
|
||||||
import datetime
|
import datetime
|
||||||
import numpy as np
|
import numpy as np
|
||||||
|
import paths_factory
|
||||||
|
|
||||||
|
|
||||||
def generate(frames, text_lines):
|
def generate(frames, text_lines):
|
||||||
|
@ -14,8 +15,6 @@ def generate(frames, text_lines):
|
||||||
if len(frames) == 0:
|
if len(frames) == 0:
|
||||||
return
|
return
|
||||||
|
|
||||||
# Get the path to the containing folder
|
|
||||||
core_path = os.path.dirname(os.path.abspath(__file__))
|
|
||||||
# Get frame dimensions
|
# Get frame dimensions
|
||||||
frame_height, frame_width, cc = frames[0].shape
|
frame_height, frame_width, cc = frames[0].shape
|
||||||
# Spread the given frames out horizontally
|
# Spread the given frames out horizontally
|
||||||
|
@ -31,7 +30,7 @@ def generate(frames, text_lines):
|
||||||
# Add the Howdy logo if there's space to do so
|
# Add the Howdy logo if there's space to do so
|
||||||
if len(frames) > 1:
|
if len(frames) > 1:
|
||||||
# Load the logo from file
|
# Load the logo from file
|
||||||
logo = cv2.imread(core_path + "/logo.png")
|
logo = cv2.imread(paths_factory.logo_path())
|
||||||
# Calculate the position of the logo
|
# Calculate the position of the logo
|
||||||
logo_y = frame_height + 20
|
logo_y = frame_height + 20
|
||||||
logo_x = frame_width * len(frames) - 210
|
logo_x = frame_width * len(frames) - 210
|
||||||
|
@ -49,19 +48,15 @@ def generate(frames, text_lines):
|
||||||
|
|
||||||
line_number += 1
|
line_number += 1
|
||||||
|
|
||||||
# Define path to any howdy logs
|
|
||||||
log_path = "/var/log/howdy"
|
|
||||||
|
|
||||||
# Made sure a snapshot folder exist
|
# Made sure a snapshot folder exist
|
||||||
if not os.path.exists(log_path):
|
if not os.path.exists(paths_factory.snapshots_dir_path()):
|
||||||
os.makedirs(log_path)
|
os.makedirs(paths_factory.snapshots_dir_path())
|
||||||
if not os.path.exists(log_path + "/snapshots"):
|
|
||||||
os.makedirs(log_path + "/snapshots")
|
|
||||||
|
|
||||||
# Generate a filename based on the current time
|
# Generate a filename based on the current time
|
||||||
filename = datetime.datetime.utcnow().strftime("%Y%m%dT%H%M%S.jpg")
|
filename = datetime.datetime.utcnow().strftime("%Y%m%dT%H%M%S.jpg")
|
||||||
|
filepath = paths_factory.snapshot_path(filename)
|
||||||
# Write the image to that file
|
# Write the image to that file
|
||||||
cv2.imwrite(log_path + "/snapshots/" + filename, snap)
|
cv2.imwrite(filepath, snap)
|
||||||
|
|
||||||
# Return the saved file location
|
# Return the saved file location
|
||||||
return log_path + "/snapshots/" + filename
|
return filepath
|
||||||
|
|
19
meson.build
Normal file
19
meson.build
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
project('howdy', 'cpp', license: 'MIT', version: 'beta', meson_version: '>= 0.64.0')
|
||||||
|
|
||||||
|
dlibdatadir = get_option('dlib_data_dir') != '' ? get_option('dlib_data_dir') : join_paths(get_option('prefix'), get_option('datadir'), 'dlib-data')
|
||||||
|
confdir = get_option('config_dir') != '' ? get_option('config_dir') : join_paths(get_option('prefix'), get_option('sysconfdir'), 'howdy')
|
||||||
|
usermodelsdir = get_option('user_models_dir') != '' ? get_option('user_models_dir') : join_paths(confdir, 'models')
|
||||||
|
logpath = get_option('log_path')
|
||||||
|
|
||||||
|
config_path = join_paths(confdir, 'config.ini')
|
||||||
|
|
||||||
|
paths_dict = {
|
||||||
|
'config_dir': confdir,
|
||||||
|
'dlib_data_dir': dlibdatadir,
|
||||||
|
'user_models_dir': usermodelsdir,
|
||||||
|
'log_path': logpath,
|
||||||
|
}
|
||||||
|
|
||||||
|
# We need to keep this order beause howdy-gtk defines the gtk script path
|
||||||
|
subdir('howdy-gtk')
|
||||||
|
subdir('howdy')
|
9
meson.options
Normal file
9
meson.options
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
option('pam_dir', type: 'string', value: '', description: 'Set the pam_howdy destination directory')
|
||||||
|
#option('fetch_dlib_data', type: 'boolean', value: false, description: 'Download dlib data files')
|
||||||
|
option('config_dir', type: 'string', value: '', description: 'Set the howdy config directory')
|
||||||
|
option('dlib_data_dir', type: 'string', value: '', description: 'Set the dlib data directory')
|
||||||
|
option('user_models_dir', type: 'string', value: '', description: 'Set the user models directory')
|
||||||
|
option('log_path', type: 'string', value: '/var/log/howdy', description: 'Set the log file path')
|
||||||
|
option('install_in_site_packages', type: 'boolean', value: false, description: 'Install howdy python files in site packages')
|
||||||
|
option('py_sources_dir', type: 'string', value: '', description: 'Set the python sources directory')
|
||||||
|
option('install_pam_config', type: 'boolean', value: false, description: 'Install pam config file (for Debian/Ubuntu)')
|
1
meson_options.txt
Symbolic link
1
meson_options.txt
Symbolic link
|
@ -0,0 +1 @@
|
||||||
|
meson.options
|
Loading…
Reference in a new issue